Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
64.15% covered (warning)
64.15%
2206 / 3439
37.33% covered (danger)
37.33%
84 / 225
CRAP
0.00% covered (danger)
0.00%
0 / 5
SeedDMS_Core_Document
68.43% covered (warning)
68.43%
984 / 1438
40.23% covered (danger)
40.23%
35 / 87
12917.80
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
4
 clearCache
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 isType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSearchFields
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
6
 getInstanceByData
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getInstance
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 applyDecorators
33.33% covered (danger)
33.33%
2 / 6
0.00% covered (danger)
0.00%
0 / 1
5.67
 getDir
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
2.50
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setName
52.94% covered (warning)
52.94%
9 / 17
0.00% covered (danger)
0.00%
0 / 1
14.67
 getComment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setComment
52.94% covered (warning)
52.94%
9 / 17
0.00% covered (danger)
0.00%
0 / 1
14.67
 getKeywords
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setKeywords
52.94% covered (warning)
52.94%
9 / 17
0.00% covered (danger)
0.00%
0 / 1
14.67
 hasCategory
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 getCategories
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
5
 setCategories
68.00% covered (warning)
68.00%
17 / 25
0.00% covered (danger)
0.00%
0 / 1
13.28
 addCategories
72.41% covered (warning)
72.41%
21 / 29
0.00% covered (danger)
0.00%
0 / 1
15.02
 removeCategories
60.00% covered (warning)
60.00%
12 / 20
0.00% covered (danger)
0.00%
0 / 1
14.18
 getDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDate
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 isDescendant
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getParent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFolder
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setParent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setFolder
71.43% covered (warning)
71.43%
25 / 35
0.00% covered (danger)
0.00%
0 / 1
16.94
 getOwner
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setOwner
69.23% covered (warning)
69.23%
18 / 26
0.00% covered (danger)
0.00%
0 / 1
12.91
 getDefaultAccess
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 setDefaultAccess
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 inheritsAccess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInheritAccess
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setInheritAccess
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 expires
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getExpires
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 setExpires
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 hasExpired
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 verifyLastestContentExpriry
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
11.10
 checkForDueRevisionWorkflow
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isLocked
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setLocked
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 getLockingUser
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 isCheckedOut
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 getCheckOutInfo
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 checkOut
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
42
 checkIn
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
90
 cancelCheckOut
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 checkOutStatus
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
42
 getSequence
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSequence
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 clearAccessList
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 getAccessList
95.83% covered (success)
95.83%
23 / 24
0.00% covered (danger)
0.00%
0 / 1
12
 addAccess
88.89% covered (warning)
88.89%
16 / 18
0.00% covered (danger)
0.00%
0 / 1
8.09
 changeAccess
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
5.05
 removeAccess
81.25% covered (warning)
81.25%
13 / 16
0.00% covered (danger)
0.00%
0 / 1
6.24
 getAccessMode
83.33% covered (warning)
83.33%
25 / 30
0.00% covered (danger)
0.00%
0 / 1
23.04
 getGroupAccessMode
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
7.12
 getNotifyList
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
10.02
 cleanNotifyList
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 addNotify
55.00% covered (warning)
55.00%
22 / 40
0.00% covered (danger)
0.00%
0 / 1
43.34
 removeNotify
80.00% covered (warning)
80.00%
16 / 20
0.00% covered (danger)
0.00%
0 / 1
8.51
 addContent
50.51% covered (warning)
50.51%
50 / 99
0.00% covered (danger)
0.00%
0 / 1
234.00
 replaceContent
72.55% covered (warning)
72.55%
37 / 51
0.00% covered (danger)
0.00%
0 / 1
24.70
 getContent
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
7.10
 getContentByVersion
71.43% covered (warning)
71.43%
15 / 21
0.00% covered (danger)
0.00%
0 / 1
13.82
 isLatestContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __getLatestContent
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 getLatestContent
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
8.12
 _removeContent
60.94% covered (warning)
60.94%
78 / 128
0.00% covered (danger)
0.00%
0 / 1
178.67
 removeContent
62.96% covered (warning)
62.96%
17 / 27
0.00% covered (danger)
0.00%
0 / 1
23.96
 getDocumentLink
82.35% covered (warning)
82.35%
14 / 17
0.00% covered (danger)
0.00%
0 / 1
8.35
 getDocumentLinks
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
10
 getReverseDocumentLinks
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
8.01
 addDocumentLink
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
10.01
 removeDocumentLink
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 getDocumentFile
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
6.02
 getDocumentFiles
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
9
 addDocumentFile
68.00% covered (warning)
68.00%
17 / 25
0.00% covered (danger)
0.00%
0 / 1
11.65
 removeDocumentFile
66.67% covered (warning)
66.67%
14 / 21
0.00% covered (danger)
0.00%
0 / 1
13.70
 remove
57.75% covered (warning)
57.75%
41 / 71
0.00% covered (danger)
0.00%
0 / 1
87.14
 __getApproversList
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getReadAccessList
68.42% covered (warning)
68.42%
39 / 57
0.00% covered (danger)
0.00%
0 / 1
47.29
 getFolderList
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 repair
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 getUsedDiskSpace
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 getTimeline
87.88% covered (warning)
87.88%
29 / 33
0.00% covered (danger)
0.00%
0 / 1
13.30
 transferToUser
62.50% covered (warning)
62.50%
15 / 24
0.00% covered (danger)
0.00%
0 / 1
7.90
SeedDMS_Core_DocumentContent
60.52% covered (warning)
60.52%
1107 / 1829
19.39% covered (danger)
19.39%
19 / 98
41068.42
0.00% covered (danger)
0.00%
0 / 1
 verifyStatus
93.22% covered (success)
93.22%
55 / 59
0.00% covered (danger)
0.00%
0 / 1
38.45
 __construct
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
3
 getInstance
83.33% covered (warning)
83.33%
15 / 18
0.00% covered (danger)
0.00%
0 / 1
6.17
 isType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getComment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOriginalFileName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFileType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFileName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __getDir
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMimeType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRevisionDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDocument
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRevisionDate
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 setDate
84.62% covered (warning)
84.62%
11 / 13
0.00% covered (danger)
0.00%
0 / 1
6.13
 getFileSize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setFileSize
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
4.32
 getChecksum
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRealChecksum
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setChecksum
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
4.32
 getRealMimeType
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setFileType
77.78% covered (warning)
77.78%
14 / 18
0.00% covered (danger)
0.00%
0 / 1
6.40
 setMimeType
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
4.32
 setComment
43.75% covered (danger)
43.75%
7 / 16
0.00% covered (danger)
0.00%
0 / 1
19.39
 getStatus
89.47% covered (warning)
89.47%
17 / 19
0.00% covered (danger)
0.00%
0 / 1
6.04
 getStatusLog
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
5.01
 setStatus
91.18% covered (success)
91.18%
31 / 34
0.00% covered (danger)
0.00%
0 / 1
16.18
 rewriteStatusLog
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
56
 getAccessMode
7.23% covered (danger)
7.23%
6 / 83
0.00% covered (danger)
0.00%
0 / 1
1589.76
 getReviewers
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
7.01
 getReviewStatus
87.88% covered (warning)
87.88%
29 / 33
0.00% covered (danger)
0.00%
0 / 1
12.26
 getReviewLog
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 rewriteReviewLog
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
240
 getApprovers
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
7.01
 getApprovalStatus
87.88% covered (warning)
87.88%
29 / 33
0.00% covered (danger)
0.00%
0 / 1
12.26
 getApproveLog
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 rewriteApprovalLog
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
240
 getRecipients
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
7.01
 getReceiptStatus
85.37% covered (warning)
85.37%
35 / 41
0.00% covered (danger)
0.00%
0 / 1
14.61
 getReceiptLog
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 rewriteReceiptLog
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
240
 getRevisors
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
7.01
 getRevisionStatus
90.00% covered (success)
90.00%
27 / 30
0.00% covered (danger)
0.00%
0 / 1
10.10
 getRevisionLog
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
4.02
 rewriteRevisionLog
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
240
 checkForDueRevisionWorkflow
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
182
 addIndReviewer
84.38% covered (warning)
84.38%
27 / 32
0.00% covered (danger)
0.00%
0 / 1
18.10
 addGrpReviewer
86.11% covered (warning)
86.11%
31 / 36
0.00% covered (danger)
0.00%
0 / 1
21.07
 setReviewByInd
77.78% covered (warning)
77.78%
21 / 27
0.00% covered (danger)
0.00%
0 / 1
13.58
 removeReview
80.95% covered (warning)
80.95%
17 / 21
0.00% covered (danger)
0.00%
0 / 1
10.69
 setReviewByGrp
76.92% covered (warning)
76.92%
20 / 26
0.00% covered (danger)
0.00%
0 / 1
13.77
 addIndApprover
84.38% covered (warning)
84.38%
27 / 32
0.00% covered (danger)
0.00%
0 / 1
18.10
 addGrpApprover
86.11% covered (warning)
86.11%
31 / 36
0.00% covered (danger)
0.00%
0 / 1
21.07
 setApprovalByInd
77.78% covered (warning)
77.78%
21 / 27
0.00% covered (danger)
0.00%
0 / 1
13.58
 removeApproval
80.95% covered (warning)
80.95%
17 / 21
0.00% covered (danger)
0.00%
0 / 1
10.69
 setApprovalByGrp
76.92% covered (warning)
76.92%
20 / 26
0.00% covered (danger)
0.00%
0 / 1
13.77
 addIndRecipient
84.38% covered (warning)
84.38%
27 / 32
0.00% covered (danger)
0.00%
0 / 1
18.10
 addGrpRecipient
87.18% covered (warning)
87.18%
34 / 39
0.00% covered (danger)
0.00%
0 / 1
20.84
 addRevisor
82.50% covered (warning)
82.50%
33 / 40
0.00% covered (danger)
0.00%
0 / 1
20.93
 addIndRevisor
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 addGrpRevisor
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 setReceiptByInd
80.00% covered (warning)
80.00%
20 / 25
0.00% covered (danger)
0.00%
0 / 1
11.97
 setReceiptByGrp
80.00% covered (warning)
80.00%
20 / 25
0.00% covered (danger)
0.00%
0 / 1
11.97
 setRevision
80.00% covered (warning)
80.00%
24 / 30
0.00% covered (danger)
0.00%
0 / 1
16.80
 setRevisionByInd
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 setRevisionByGrp
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 delIndReviewer
70.59% covered (warning)
70.59%
12 / 17
0.00% covered (danger)
0.00%
0 / 1
11.06
 delGrpReviewer
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
72
 delIndApprover
72.22% covered (warning)
72.22%
13 / 18
0.00% covered (danger)
0.00%
0 / 1
9.37
 delGrpApprover
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
72
 delIndRecipient
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
56
 delGrpRecipient
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
56
 delRevisor
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
182
 delIndRevisor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 delGrpRevisor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 startRevision
80.00% covered (warning)
80.00%
20 / 25
0.00% covered (danger)
0.00%
0 / 1
8.51
 finishRevision
76.00% covered (warning)
76.00%
19 / 25
0.00% covered (danger)
0.00%
0 / 1
11.38
 setWorkflowState
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
3.14
 getWorkflowState
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
5.01
 setWorkflow
66.67% covered (warning)
66.67%
14 / 21
0.00% covered (danger)
0.00%
0 / 1
8.81
 getWorkflow
88.24% covered (warning)
88.24%
15 / 17
0.00% covered (danger)
0.00%
0 / 1
6.06
 rewriteWorkflowLog
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
72
 rewindWorkflow
76.92% covered (warning)
76.92%
10 / 13
0.00% covered (danger)
0.00%
0 / 1
3.11
 removeWorkflow
83.33% covered (warning)
83.33%
20 / 24
0.00% covered (danger)
0.00%
0 / 1
8.30
 getParentWorkflow
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
56
 runSubWorkflow
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 returnFromSubWorkflow
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
56
 triggerWorkflowTransitionIsAllowed
66.67% covered (warning)
66.67%
18 / 27
0.00% covered (danger)
0.00%
0 / 1
19.26
 executeWorkflowTransitionIsAllowed
52.78% covered (warning)
52.78%
19 / 36
0.00% covered (danger)
0.00%
0 / 1
42.96
 triggerWorkflowTransition
63.16% covered (warning)
63.16%
12 / 19
0.00% covered (danger)
0.00%
0 / 1
13.05
 enterNextState
80.00% covered (warning)
80.00%
20 / 25
0.00% covered (danger)
0.00%
0 / 1
11.97
 getWorkflowLog
86.36% covered (warning)
86.36%
19 / 22
0.00% covered (danger)
0.00%
0 / 1
8.16
 getLastWorkflowLog
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
3.01
 needsWorkflowAction
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 repair
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
132
SeedDMS_Core_DocumentLink
85.00% covered (warning)
85.00%
17 / 20
87.50% covered (warning)
87.50%
7 / 8
13.57
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 isType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getID
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDocument
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTarget
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isPublic
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAccessMode
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
6.00
SeedDMS_Core_DocumentFile
88.31% covered (warning)
88.31%
68 / 77
86.96% covered (warning)
86.96%
20 / 23
42.55
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
 isType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getID
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDocument
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUserID
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getComment
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setComment
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getDate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDate
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 getDir
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFileType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMimeType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRealMimeType
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getOriginalFileName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setName
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getUser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVersion
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setVersion
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 isPublic
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPublic
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 getAccessMode
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
6.00
SeedDMS_Core_AddContentResultSet
22.41% covered (danger)
22.41%
13 / 58
33.33% covered (danger)
33.33%
3 / 9
865.86
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 setDMS
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addReviewer
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
132
 addApprover
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
132
 setStatus
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
4.59
 getStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getReviewers
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
42
 getApprovers
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2declare(strict_types=1);
3
4/**
5 * Implementation of a document in the document management system
6 *
7 * @category   DMS
8 * @package    SeedDMS_Core
9 * @license    GPL2
10 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
11 *             Uwe Steinmann <uwe@steinmann.cx>
12 * @copyright  Copyright (C) 2002-2005 Markus Westphal, 2006-2008 Malcolm Cowe,
13 *             2010 Matteo Lucarelli, 2010-2024 Uwe Steinmann
14 * @version    Release: @package_version@
15 */
16
17/**
18 * The different states a document can be in
19 */
20/*
21 * Document is in review state. A document is in review state when
22 * it needs to be reviewed by a user or group.
23 */
24define("S_DRAFT_REV", 0);
25
26/*
27 * Document is in approval state. A document is in approval state when
28 * it needs to be approved by a user or group.
29 */
30define("S_DRAFT_APP", 1);
31
32/*
33 * Document is released. A document is in release state either when
34 * it needs no review or approval after uploaded or has been reviewed
35 * and/or approved.
36 */
37define("S_RELEASED",  2);
38
39/*
40 * Document is in workflow. A document is in workflow if a workflow
41 * has been started and has not reached a final state.
42 */
43define("S_IN_WORKFLOW",  3);
44
45/*
46 * Document is in a revision workflow. A revision workflow is started
47 * some time after the document has been released.
48 */
49define("S_IN_REVISION",  4);
50
51/*
52 * Document is in draft status. Being in draft means that the document
53 * is still worked on. This status is mainly for uploading documents
54 * which aren't fully complete but needs to accessible for the public,
55 * e.g. in order to colaborate on them.
56 */
57define("S_DRAFT",  5);
58
59/*
60 * Document needs correction after revision. This needs to be different from
61 * the regular S_REJECTED because documents which has been rejected
62 * in revision are not necessarily invalid but just needs correction.
63 */
64define("S_NEEDS_CORRECTION",  6);
65
66/*
67 * Document was rejected. A document is in rejected state when
68 * the review failed or approval was not given.
69 */
70define("S_REJECTED", -1);
71
72/*
73 * Document is obsolete. A document can be obsoleted once it was
74 * released.
75 */
76define("S_OBSOLETE", -2);
77
78/*
79 * Document is expired. A document expires when the expiration date
80 * is reached
81 */
82define("S_EXPIRED",  -3);
83
84/*
85 * Lowest and highest status that may be set
86 */
87define("S_LOWEST_STATUS",  -3);
88define("S_HIGHEST_STATUS",  6);
89
90/**
91 * The different states a workflow log can be in. This is used in
92 * all tables tblDocumentXXXLog
93 */
94/*
95 * workflow is in a neutral status waiting for action of user
96 */
97define("S_LOG_WAITING",  0);
98
99/*
100 * workflow has been successful ended. The document content has been
101 * approved, reviewed, aknowledged or revised
102 */
103define("S_LOG_ACCEPTED",  1);
104
105/*
106 * workflow has been unsuccessful ended. The document content has been
107 * rejected
108 */
109define("S_LOG_REJECTED",  -1);
110
111/*
112 * user has been removed from workflow. This can be for different reasons
113 * 1. the user has been actively removed from the workflow, 2. the user has
114 * been deleted.
115 */
116define("S_LOG_USER_REMOVED",  -2);
117
118/*
119 * workflow is sleeping until reactivation. The workflow has been set up
120 * but not started. This is only valid for the revision workflow, which
121 * may run over and over again.
122 */
123define("S_LOG_SLEEPING",  -3);
124
125/**
126 * Class to represent a document in the document management system
127 *
128 * A document in SeedDMS is a collection of content elements which are
129 * similar to a file in a regular file system.
130 * Documents may have any number of content elements
131 * ({@see SeedDMS_Core_DocumentContent}). These content elements are often
132 * called versions ordered in a timely manner. The most recent content element
133 * is the current version of the document.
134 *
135 * Documents can be linked to other documents, can have attached files,
136 * can be assigned to a category and have additional attributes.
137 * The document content can be anything that can be stored in a regular
138 * file.
139 *
140 * @category   DMS
141 * @package    SeedDMS_Core
142 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
143 *             Uwe Steinmann <uwe@steinmann.cx>
144 * @copyright  Copyright (C) 2002-2005 Markus Westphal, 2006-2008 Malcolm Cowe,
145 *             2010 Matteo Lucarelli, 2010-2024 Uwe Steinmann
146 * @version    Release: @package_version@
147 */
148class SeedDMS_Core_Document extends SeedDMS_Core_Object { /* {{{ */
149    /**
150     * @var string name of document
151     */
152    protected $_name;
153
154    /**
155     * @var string comment of document
156     */
157    protected $_comment;
158
159    /**
160     * @var integer unix timestamp of creation date
161     */
162    protected $_date;
163
164    /**
165     * @var integer id of user who is the owner
166     */
167    protected $_ownerID;
168
169    /**
170     * @var object user who is the owner
171     */
172    protected $_owner;
173
174    /**
175     * @var integer id of folder this document belongs to
176     */
177    protected $_folderID;
178
179    /**
180     * @var object parent folder this document belongs to
181     */
182    protected $_parent;
183
184    /**
185     * @var integer timestamp of expiration date
186     */
187    protected $_expires;
188
189    /**
190     * @var boolean true if access is inherited, otherwise false
191     */
192    protected $_inheritAccess;
193
194    /**
195     * @var integer default access if access rights are not inherited
196     */
197    protected $_defaultAccess;
198
199    /**
200     * @var array list of notifications for users and groups
201     */
202    protected $_readAccessList;
203
204    /**
205     * @var array list of notifications for users and groups
206     */
207    public $_notifyList;
208
209    /**
210     * @var boolean true if document is locked, otherwise false
211     */
212    protected $_locked;
213
214    /**
215     * @var object user who has locked the document
216     */
217    protected $_lockingUser;
218
219    /**
220     * @var string list of keywords
221     */
222    protected $_keywords;
223
224    /**
225     * @var SeedDMS_Core_DocumentCategory[] list of categories
226     */
227    protected $_categories;
228
229    /**
230     * @var integer position of document within the parent folder
231     */
232    protected $_sequence;
233
234    /**
235     * @var SeedDMS_Core_DocumentContent temp. storage for latestcontent
236     */
237    protected $_latestContent;
238
239    /**
240     * @var array temp. storage for content
241     */
242    protected $_content;
243
244    /**
245     * @var SeedDMS_Core_Folder
246     */
247    protected $_folder;
248
249    /** @var array of SeedDMS_Core_UserAccess and SeedDMS_Core_GroupAccess */
250    protected $_accessList;
251
252    /**
253     * @var array
254     */
255    protected $_documentLinks;
256
257    /**
258     * @var array
259     */
260    protected $_documentFiles;
261
262    public function __construct($id, $name, $comment, $date, $expires, $ownerID, $folderID, $inheritAccess, $defaultAccess, $locked, $keywords, $sequence) { /* {{{ */
263        parent::__construct($id);
264        $this->_name = $name;
265        $this->_comment = $comment;
266        $this->_date = $date;
267        $this->_expires = $expires;
268        $this->_ownerID = $ownerID;
269        $this->_folderID = $folderID;
270        $this->_inheritAccess = $inheritAccess ? true : false;
271        $this->_defaultAccess = $defaultAccess;
272        $this->_locked = ($locked == null || $locked == '' ? -1 : $locked);
273        $this->_lockingUser = null;
274        $this->_keywords = $keywords;
275        $this->_sequence = $sequence;
276        $this->_categories = array();
277        $this->_notifyList = array();
278        $this->_latestContent = null;
279        $this->_content = null;
280        /* Cache */
281        $this->clearCache();
282    } /* }}} */
283
284    /**
285     * Clear cache of this instance.
286     *
287     * The result of some expensive database actions (e.g. get all subfolders
288     * or documents) will be saved in a class variable to speed up consecutive
289     * calls of the same method. If a second call of the same method shall not
290     * use the cache, then it must be cleared.
291     *
292     */
293    public function clearCache() { /* {{{ */
294        $this->_parent = null;
295        $this->_owner = null;
296        $this->_documentLinks = null;
297        $this->_documentFiles = null;
298        $this->_content = null;
299        $this->_accessList = null;
300        $this->_notifyList = array();
301        $this->_readAccessList = array();
302    } /* }}} */
303
304    /**
305     * Check if this object is of type 'document'.
306     *
307     * @param string $type type of object
308     */
309    public function isType($type) { /* {{{ */
310        return $type == 'document';
311    } /* }}} */
312
313    /**
314     * Return an array of database fields which are used for searching
315     * a term entered in the database search form
316     *
317     * @param SeedDMS_Core_DMS $dms
318     * @param array $searchin integer list of search scopes (2=name, 3=comment,
319     * 4=attributes)
320     * @return array list of database fields
321     */
322    public static function getSearchFields($dms, $searchin) { /* {{{ */
323        $db = $dms->getDB();
324
325        $searchFields = array();
326        if (in_array(1, $searchin)) {
327            $searchFields[] = "`tblDocuments`.`keywords`";
328        }
329        if (in_array(2, $searchin)) {
330            $searchFields[] = "`tblDocuments`.`name`";
331        }
332        if (in_array(3, $searchin)) {
333            $searchFields[] = "`tblDocuments`.`comment`";
334            $searchFields[] = "`tblDocumentContent`.`comment`";
335        }
336        if (in_array(4, $searchin)) {
337            $searchFields[] = "`tblDocumentAttributes`.`value`";
338            $searchFields[] = "`tblDocumentContentAttributes`.`value`";
339        }
340        if (in_array(5, $searchin)) {
341            $searchFields[] = $db->castToText("`tblDocuments`.`id`");
342        }
343
344        return $searchFields;
345    } /* }}} */
346
347    /**
348     * Return a folder by its database record
349     *
350     * @param array $resArr array of folder data as returned by database
351     * @param SeedDMS_Core_DMS $dms
352     * @return SeedDMS_Core_Folder|bool instance of SeedDMS_Core_Folder if document exists
353     */
354    public static function getInstanceByData($resArr, $dms) { /* {{{ */
355        $classname = $dms->getClassname('document');
356        /** @var SeedDMS_Core_Document $document */
357        $document = new $classname($resArr["id"], $resArr["name"], $resArr["comment"], $resArr["date"], $resArr["expires"], $resArr["owner"], $resArr["folder"], $resArr["inheritAccess"], $resArr["defaultAccess"], $resArr['lock'], $resArr["keywords"], $resArr["sequence"]);
358        $document->setDMS($dms);
359        $document = $document->applyDecorators();
360        return $document;
361    } /* }}} */
362
363    /**
364     * Return an document by its id
365     *
366     * @param integer $id id of document
367     * @param SeedDMS_Core_DMS $dms
368     * @return bool|SeedDMS_Core_Document instance of SeedDMS_Core_Document if document exists, null
369     * if document does not exist, false in case of error
370     */
371    public static function getInstance($id, $dms) { /* {{{ */
372        $db = $dms->getDB();
373
374//        $queryStr = "SELECT * FROM `tblDocuments` WHERE `id` = " . (int) $id;
375        $queryStr = "SELECT `tblDocuments`.*, `tblDocumentLocks`.`userID` as `lock` FROM `tblDocuments` LEFT JOIN `tblDocumentLocks` ON `tblDocuments`.`id` = `tblDocumentLocks`.`document` WHERE `id` = " . (int) $id;
376        if($dms->checkWithinRootDir)
377            $queryStr .= " AND `folderList` LIKE '%:".$dms->rootFolderID.":%'";
378        $resArr = $db->getResultArray($queryStr);
379        if (is_bool($resArr) && $resArr == false)
380            return false;
381        if (count($resArr) != 1)
382            return null;
383        $resArr = $resArr[0];
384
385        $resArr['lock'] = !$resArr['lock'] ? -1 : $resArr['lock'];
386
387        return self::getInstanceByData($resArr, $dms);
388    } /* }}} */
389
390    /**
391     * Apply decorators
392     *
393     * @return object final object after all decorators has been applied
394     */
395    public function applyDecorators() { /* {{{ */
396        if($decorators = $this->_dms->getDecorators('document')) {
397            $s = $this;
398            foreach($decorators as $decorator) {
399                $s = new $decorator($s);
400            }
401            return $s;
402        } else {
403            return $this;
404        }
405    } /* }}} */
406
407    /**
408     * Return the directory of the document in the file system relativ
409     * to the contentDir
410     *
411     * @return string directory of document
412     */
413    public function getDir() { /* {{{ */
414        if($this->_dms->maxDirID) {
415            $dirid = (int) (($this->_id-1) / $this->_dms->maxDirID) + 1;
416            return $dirid.DIRECTORY_SEPARATOR.$this->_id.DIRECTORY_SEPARATOR;
417        } else {
418            return $this->_id.DIRECTORY_SEPARATOR;
419        }
420    } /* }}} */
421
422    /**
423     * Return the name of the document
424     *
425     * @return string name of document
426     */
427    public function getName() { return $this->_name; }
428
429    /**
430     * Set the name of the document
431     *
432     * @param $newName string new name of document
433     * @return bool
434     */
435    public function setName($newName) { /* {{{ */
436        $db = $this->_dms->getDB();
437
438        /* Check if 'onPreSetName' callback is set */
439        if(isset($this->_dms->callbacks['onPreSetName'])) {
440            foreach($this->_dms->callbacks['onPreSetName'] as $callback) {
441                $ret = call_user_func($callback[0], $callback[1], $this, $newName);
442                if(is_bool($ret))
443                    return $ret;
444            }
445        }
446
447        $queryStr = "UPDATE `tblDocuments` SET `name` = ".$db->qstr($newName)." WHERE `id` = ". $this->_id;
448        if (!$db->getResult($queryStr))
449            return false;
450
451        $oldName = $this->_name;
452        $this->_name = $newName;
453
454        /* Check if 'onPostSetName' callback is set */
455        if(isset($this->_dms->callbacks['onPostSetName'])) {
456            foreach($this->_dms->callbacks['onPostSetName'] as $callback) {
457                $ret = call_user_func($callback[0], $callback[1], $this, $oldName);
458                if(is_bool($ret))
459                    return $ret;
460            }
461        }
462
463        return true;
464    } /* }}} */
465
466    /**
467     * Return the comment of the document
468     *
469     * @return string comment of document
470     */
471    public function getComment() { return $this->_comment; }
472
473    /**
474     * Set the comment of the document
475     *
476     * @param $newComment string new comment of document
477     * @return bool
478     */
479    public function setComment($newComment) { /* {{{ */
480        $db = $this->_dms->getDB();
481
482        /* Check if 'onPreSetComment' callback is set */
483        if(isset($this->_dms->callbacks['onPreSetComment'])) {
484            foreach($this->_dms->callbacks['onPreSetComment'] as $callback) {
485                $ret = call_user_func($callback[0], $callback[1], $this, $newComment);
486                if(is_bool($ret))
487                    return $ret;
488            }
489        }
490
491        $queryStr = "UPDATE `tblDocuments` SET `comment` = ".$db->qstr($newComment)." WHERE `id` = ". $this->_id;
492        if (!$db->getResult($queryStr))
493            return false;
494
495        $oldComment = $this->_comment;
496        $this->_comment = $newComment;
497
498        /* Check if 'onPostSetComment' callback is set */
499        if(isset($this->_dms->callbacks['onPostSetComment'])) {
500            foreach($this->_dms->callbacks['onPostSetComment'] as $callback) {
501                $ret = call_user_func($callback[0], $callback[1], $this, $oldComment);
502                if(is_bool($ret))
503                    return $ret;
504            }
505        }
506
507        return true;
508    } /* }}} */
509
510    /**
511     * @return string
512     */
513    public function getKeywords() { return $this->_keywords; }
514
515    /**
516     * @param string $newKeywords
517     * @return bool
518     */
519    public function setKeywords($newKeywords) { /* {{{ */
520        $db = $this->_dms->getDB();
521
522        /* Check if 'onPreSetKeywords' callback is set */
523        if(isset($this->_dms->callbacks['onPreSetKeywords'])) {
524            foreach($this->_dms->callbacks['onPreSetKeywords'] as $callback) {
525                $ret = call_user_func($callback[0], $callback[1], $this, $newKeywords);
526                if(is_bool($ret))
527                    return $ret;
528            }
529        }
530
531        $queryStr = "UPDATE `tblDocuments` SET `keywords` = ".$db->qstr($newKeywords)." WHERE `id` = ". $this->_id;
532        if (!$db->getResult($queryStr))
533            return false;
534
535        $oldKeywords = $this->_keywords;
536        $this->_keywords = $newKeywords;
537
538        /* Check if 'onPostSetKeywords' callback is set */
539        if(isset($this->_dms->callbacks['onPostSetKeywords'])) {
540            foreach($this->_dms->callbacks['onPostSetKeywords'] as $callback) {
541                $ret = call_user_func($callback[0], $callback[1], $this, $oldKeywords);
542                if(is_bool($ret))
543                    return $ret;
544            }
545        }
546
547        return true;
548    } /* }}} */
549
550    /**
551     * Check if document has a given category
552     *
553     * @param SeedDMS_Core_DocumentCategory $cat
554     * @return bool true if document has category, otherwise false
555     */
556    public function hasCategory($cat) { /* {{{ */
557        $db = $this->_dms->getDB();
558
559        if(!$cat)
560            return false;
561
562        $queryStr = "SELECT * FROM `tblDocumentCategory` WHERE `documentID` = ".$this->_id." AND `categoryID`=".$cat->getId();
563        $resArr = $db->getResultArray($queryStr);
564        if (!$resArr)
565            return false;
566
567        return true;
568    } /* }}} */
569
570    /**
571     * Retrieve a list of all categories this document belongs to
572     *
573     * @return bool|SeedDMS_Core_DocumentCategory[]
574     */
575    public function getCategories() { /* {{{ */
576        $db = $this->_dms->getDB();
577
578        if(!$this->_categories) {
579            $queryStr = "SELECT * FROM `tblCategory` WHERE `id` IN (SELECT `categoryID` FROM `tblDocumentCategory` WHERE `documentID` = ".$this->_id.")";
580            $resArr = $db->getResultArray($queryStr);
581            if (is_bool($resArr) && !$resArr)
582                return false;
583
584            $this->_categories = [];
585            foreach ($resArr as $row) {
586                $cat = new SeedDMS_Core_DocumentCategory($row['id'], $row['name']);
587                $cat->setDMS($this->_dms);
588                $this->_categories[] = $cat;
589            }
590        }
591        return $this->_categories;
592    } /* }}} */
593
594    /**
595     * Set a list of categories for the document
596     *
597     * This method will delete currently assigned categories and sets new
598     * categories.
599     *
600     * @param SeedDMS_Core_DocumentCategory[] $newCategories list of category objects
601     * @return bool
602     */
603    public function setCategories($newCategories) { /* {{{ */
604        $db = $this->_dms->getDB();
605
606        /* Check if 'onPreSetCategories' callback is set */
607        if(isset($this->_dms->callbacks['onPreSetCategories'])) {
608            foreach($this->_dms->callbacks['onPreSetCategories'] as $callback) {
609                $ret = call_user_func($callback[0], $callback[1], $this, $newCategories);
610                if(is_bool($ret))
611                    return $ret;
612            }
613        }
614
615        $db->startTransaction();
616        $queryStr = "DELETE FROM `tblDocumentCategory` WHERE `documentID` = ". $this->_id;
617        if (!$db->getResult($queryStr)) {
618            $db->rollbackTransaction();
619            return false;
620        }
621
622        foreach($newCategories as $cat) {
623            $queryStr = "INSERT INTO `tblDocumentCategory` (`categoryID`, `documentID`) VALUES (". $cat->getId() .", ". $this->_id .")";
624            if (!$db->getResult($queryStr)) {
625                $db->rollbackTransaction();
626                return false;
627            }
628        }
629
630        $db->commitTransaction();
631
632        $oldCategories = $this->_categories;
633        $this->_categories = $newCategories;
634
635        /* Check if 'onPostSetCategories' callback is set */
636        if(isset($this->_dms->callbacks['onPostSetCategories'])) {
637            foreach($this->_dms->callbacks['onPostSetCategories'] as $callback) {
638                $ret = call_user_func($callback[0], $callback[1], $this, $oldCategories);
639                if(is_bool($ret))
640                    return $ret;
641            }
642        }
643
644        return true;
645    } /* }}} */
646
647    /**
648     * Add a list of categories to the document
649     *
650     * This method will add a list of new categories to the document.
651     *
652     * @param array $newCategories list of category objects
653     */
654    public function addCategories($newCategories) { /* {{{ */
655        $db = $this->_dms->getDB();
656
657        /* Check if 'onPreAddCategories' callback is set */
658        if(isset($this->_dms->callbacks['onPreAddCategories'])) {
659            foreach($this->_dms->callbacks['onPreAddCategories'] as $callback) {
660                $ret = call_user_func($callback[0], $callback[1], $this, $newCategories);
661                if(is_bool($ret))
662                    return $ret;
663            }
664        }
665
666        if(!$this->_categories)
667            $this->getCategories();
668
669        $catids = array();
670        foreach($this->_categories as $cat)
671            $catids[] = $cat->getID();
672
673        $db->startTransaction();
674        $ncat = array(); // Array containing actually added new categories
675        foreach($newCategories as $cat) {
676            if(!in_array($cat->getID(), $catids)) {
677                $queryStr = "INSERT INTO `tblDocumentCategory` (`categoryID`, `documentID`) VALUES (". $cat->getId() .", ". $this->_id .")";
678                if (!$db->getResult($queryStr)) {
679                    $db->rollbackTransaction();
680                    return false;
681                }
682                $ncat[] = $cat;
683            }
684        }
685        $db->commitTransaction();
686
687        $oldCategories = $this->_categories;
688        $this->_categories = array_merge($this->_categories, $ncat);
689
690        /* Check if 'onPostAddCategories' callback is set */
691        if(isset($this->_dms->callbacks['onPostAddCategories'])) {
692            foreach($this->_dms->callbacks['onPostAddCategories'] as $callback) {
693                $ret = call_user_func($callback[0], $callback[1], $this, $oldCategories);
694                if(is_bool($ret))
695                    return $ret;
696            }
697        }
698
699        return true;
700    } /* }}} */
701
702    /**
703     * Remove a list of categories from the document
704     *
705     * This method will remove a list of assigned categories to the document.
706     *
707     * @param array $newCategories list of category objects
708     */
709    public function removeCategories($categories) { /* {{{ */
710        $db = $this->_dms->getDB();
711
712        /* Check if 'onPreRemoveCategories' callback is set */
713        if(isset($this->_dms->callbacks['onPreRemoveCategories'])) {
714            foreach($this->_dms->callbacks['onPreRemoveCategories'] as $callback) {
715                $ret = call_user_func($callback[0], $callback[1], $this, $categories);
716                if(is_bool($ret))
717                    return $ret;
718            }
719        }
720
721        $catids = array();
722        foreach($categories as $cat)
723            $catids[] = $cat->getID();
724
725        $queryStr = "DELETE FROM `tblDocumentCategory` WHERE `documentID` = ". $this->_id ." AND `categoryID` IN (".implode(',', $catids).")";
726        if (!$db->getResult($queryStr)) {
727            return false;
728        }
729
730        $oldCategories = $this->_categories;
731        $this->_categories = null;
732
733        /* Check if 'onPostRemoveCategories' callback is set */
734        if(isset($this->_dms->callbacks['onPostRemoveCategories'])) {
735            foreach($this->_dms->callbacks['onPostRemoveCategories'] as $callback) {
736                $ret = call_user_func($callback[0], $callback[1], $this, $oldCategories);
737                if(is_bool($ret))
738                    return $ret;
739            }
740        }
741
742        return true;
743    } /* }}} */
744
745    /**
746     * Return creation date of the document
747     *
748     * @return integer unix timestamp of creation date
749     */
750    public function getDate() { /* {{{ */
751        return $this->_date;
752    } /* }}} */
753
754    /**
755     * Set creation date of the document
756     *
757     * @param integer $date timestamp of creation date. If false then set it
758     * to the current timestamp
759     * @return boolean true on success
760     */
761    public function setDate($date) { /* {{{ */
762        $db = $this->_dms->getDB();
763
764        if(!$date)
765            $date = time();
766        else {
767            if(!is_numeric($date))
768                return false;
769        }
770
771        $queryStr = "UPDATE `tblDocuments` SET `date` = " . (int) $date . " WHERE `id` = ". $this->_id;
772        if (!$db->getResult($queryStr))
773            return false;
774        $this->_date = $date;
775        return true;
776    } /* }}} */
777
778    /**
779     * Check, if this document is a child of a given folder
780     *
781     * @param object $folder parent folder
782     * @return boolean true if document is a direct child of the given folder
783     */
784    public function isDescendant($folder) { /* {{{ */
785        /* First check if the parent folder is folder looking for */
786        if ($this->getFolder()->getID() == $folder->getID())
787            return true;
788        /* Second, check for the parent folder of this document to be
789         * below the given folder
790         */
791        if($this->getFolder()->isDescendant($folder))
792            return true;
793        return false;
794    } /* }}} */
795
796    /**
797     * Return the parent folder of the document
798     *
799     * @see SeedDMS_Core_Document::getFolder()
800     *
801     * @return SeedDMS_Core_Folder parent folder
802     */
803    public function getParent() { /* {{{ */
804        return $this->getFolder();
805    } /* }}} */
806
807    /**
808     * Return the parent folder of the document
809     *
810     * @return SeedDMS_Core_Folder parent folder
811     */
812    public function getFolder() { /* {{{ */
813        if (!isset($this->_folder))
814            $this->_folder = $this->_dms->getFolder($this->_folderID);
815        return $this->_folder;
816    } /* }}} */
817
818    /**
819     * Set folder of a document
820     *
821     * This method basically moves a document from a folder to another
822     * folder.
823     *
824     * @param SeedDMS_Core_Folder $newFolder
825     * @return boolean false in case of an error, otherwise true
826     */
827    public function setParent($newFolder) { /* {{{ */
828        return $this->setFolder($newFolder);
829    } /* }}} */
830
831    /**
832     * Set folder of a document
833     *
834     * This method basically moves a document from a folder to another
835     * folder.
836     *
837     * @param SeedDMS_Core_Folder $newFolder
838     * @return boolean false in case of an error, otherwise true
839     */
840    public function setFolder($newFolder) { /* {{{ */
841        $db = $this->_dms->getDB();
842
843        if(!$newFolder)
844            return false;
845
846        if(!$newFolder->isType('folder'))
847            return false;
848
849        /* Check if 'onPreSetFolder' callback is set */
850        if(isset($this->_dms->callbacks['onPreSetFolder'])) {
851            foreach($this->_dms->callbacks['onPreSetFolder'] as $callback) {
852                $ret = call_user_func($callback[0], $callback[1], $this, $newFolder);
853                if(is_bool($ret))
854                    return $ret;
855            }
856        }
857
858        $db->startTransaction();
859
860        $queryStr = "UPDATE `tblDocuments` SET `folder` = " . $newFolder->getID() . " WHERE `id` = ". $this->_id;
861        if (!$db->getResult($queryStr)) {
862            $db->rollbackTransaction();
863            return false;
864        }
865
866        // Make sure that the folder search path is also updated.
867        $path = $newFolder->getPath();
868        $flist = "";
869        /** @var SeedDMS_Core_Folder[] $path */
870        foreach ($path as $f) {
871            $flist .= ":".$f->getID();
872        }
873        if (strlen($flist)>1) {
874            $flist .= ":";
875        }
876        $queryStr = "UPDATE `tblDocuments` SET `folderList` = '" . $flist . "' WHERE `id` = ". $this->_id;
877        if (!$db->getResult($queryStr)) {
878            $db->rollbackTransaction();
879            return false;
880        }
881
882        $db->commitTransaction();
883
884        $oldFolder = $this->_folder;
885        $this->_folderID = $newFolder->getID();
886        $this->_folder = $newFolder;
887
888        /* Check if 'onPostSetFolder' callback is set */
889        if(isset($this->_dms->callbacks['onPostSetFolder'])) {
890            foreach($this->_dms->callbacks['onPostSetFolder'] as $callback) {
891                $ret = call_user_func($callback[0], $callback[1], $this, $oldFolder);
892                if(is_bool($ret))
893                    return $ret;
894            }
895        }
896
897        return true;
898    } /* }}} */
899
900    /**
901     * Return owner of document
902     *
903     * @return SeedDMS_Core_User owner of document as an instance of {@see SeedDMS_Core_User}
904     */
905    public function getOwner() { /* {{{ */
906        if (!isset($this->_owner))
907            $this->_owner = $this->_dms->getUser($this->_ownerID);
908        return $this->_owner;
909    } /* }}} */
910
911    /**
912     * Set owner of a document
913     *
914     * @param SeedDMS_Core_User $newOwner new owner
915     * @return boolean true if successful otherwise false
916     */
917    public function setOwner($newOwner) { /* {{{ */
918        $db = $this->_dms->getDB();
919
920        if(!$newOwner)
921            return false;
922
923        if(!$newOwner->isType('user'))
924            return false;
925
926        $oldOwner = self::getOwner();
927
928        $db->startTransaction();
929
930        /* Check if 'onPreSetOwner' callback is set */
931        if(isset($this->_dms->callbacks['onPreSetOwner'])) {
932            foreach($this->_dms->callbacks['onPreSetOwner'] as $callback) {
933                $ret = call_user_func($callback[0], $callback[1], $this, $newOwner);
934                if(is_bool($ret))
935                    return $ret;
936            }
937        }
938
939        $queryStr = "UPDATE `tblDocuments` set `owner` = " . $newOwner->getID() . " WHERE `id` = " . $this->_id;
940        if (!$db->getResult($queryStr)) {
941            $db->rollbackTransaction();
942            return false;
943        }
944
945        /* FIXME: Update also all locks and checkouts done by the previous owner */
946        /*
947        $queryStr = "UPDATE `tblDocumentLocks` set `userID` = " . $newOwner->getID() . " WHERE `document` = " . $this->_id . " AND `userID` = " . $oldOwner->getID();
948        if (!$db->getResult($queryStr)) {
949            $db->rollbackTransaction();
950            return false;
951        }
952
953        $queryStr = "UPDATE `tblDocumentCheckOuts` set `userID` = " . $newOwner->getID() . " WHERE `document` = " . $this->_id . " AND `userID` = " . $oldOwner->getID();
954        if (!$db->getResult($queryStr)) {
955            $db->rollbackTransaction();
956            return false;
957        }
958         */
959
960        $db->commitTransaction();
961
962        $this->_ownerID = $newOwner->getID();
963        $this->_owner = $newOwner;
964
965        $this->_readAccessList = array();
966
967        /* Check if 'onPostSetOwner' callback is set */
968        if(isset($this->_dms->callbacks['onPostSetOwner'])) {
969            foreach($this->_dms->callbacks['onPostSetOwner'] as $callback) {
970                $ret = call_user_func($callback[0], $callback[1], $this, $oldOwner);
971                if(is_bool($ret))
972                    return $ret;
973            }
974        }
975
976        return true;
977    } /* }}} */
978
979    /**
980     * @return bool|int
981     */
982    public function getDefaultAccess() { /* {{{ */
983        if ($this->inheritsAccess()) {
984            $res = $this->getFolder();
985            if (!$res) return false;
986            return $this->_folder->getDefaultAccess();
987        }
988        return $this->_defaultAccess;
989    } /* }}} */
990
991    /**
992     * Set default access mode
993     *
994     * This method sets the default access mode and also removes all notifiers which
995     * will not have read access anymore. Setting a default access mode will only
996     * have an immediate effect if the access rights are not inherited, otherwise
997     * it just updates the database record of the document and once the
998     * inheritance is turn off the default access mode will take effect.
999     *
1000     * @param integer     $mode    access mode
1001     * @param bool|string $noclean set to true if notifier list shall not be clean up
1002     *
1003     * @return bool
1004     */
1005    public function setDefaultAccess($mode, $noclean=false) { /* {{{ */
1006        $db = $this->_dms->getDB();
1007
1008        if($mode < M_LOWEST_RIGHT || $mode > M_HIGHEST_RIGHT)
1009            return false;
1010
1011        $queryStr = "UPDATE `tblDocuments` set `defaultAccess` = " . (int) $mode . " WHERE `id` = " . $this->_id;
1012        if (!$db->getResult($queryStr))
1013            return false;
1014
1015        $this->_defaultAccess = $mode;
1016        $this->_readAccessList = array();
1017
1018        /* Setting the default access mode does not have any effect if access
1019         * is still inherited. In that case there is no need to clean the
1020         * notification list.
1021         */
1022        if(!$noclean && !$this->_inheritAccess)
1023            $this->cleanNotifyList();
1024
1025        return true;
1026    } /* }}} */
1027
1028    /**
1029     * @return bool
1030     */
1031    public function inheritsAccess() { return $this->_inheritAccess; }
1032
1033    /**
1034     * This is supposed to be a replacement for inheritsAccess()
1035     *
1036     * @return bool
1037     */
1038    public function getInheritAccess() { return $this->_inheritAccess; }
1039
1040    /**
1041     * Set inherited access mode
1042     *
1043     * Setting inherited access mode will set or unset the internal flag which
1044     * controls if the access mode is inherited from the parent folder or not.
1045     * It will not modify the
1046     * access control list for the current object. It will remove all
1047     * notifications of users which do not even have read access anymore
1048     * after setting or unsetting inherited access.
1049     *
1050     * @param boolean $inheritAccess set to true for setting and false for
1051     *        unsetting inherited access mode
1052     * @param boolean $noclean set to true if notifier list shall not be clean up
1053     * @return boolean true if operation was successful otherwise false
1054     */
1055    public function setInheritAccess($inheritAccess, $noclean=false) { /* {{{ */
1056        $db = $this->_dms->getDB();
1057
1058        $queryStr = "UPDATE `tblDocuments` SET `inheritAccess` = " . ($inheritAccess ? "1" : "0") . " WHERE `id` = " . $this->_id;
1059        if (!$db->getResult($queryStr))
1060            return false;
1061
1062        $this->_inheritAccess = ($inheritAccess ? true : false);
1063        $this->_readAccessList = array();
1064
1065        if(!$noclean)
1066            $this->cleanNotifyList();
1067
1068        return true;
1069    } /* }}} */
1070
1071    /**
1072     * Check if document expires
1073     *
1074     * @return boolean true if document has expiration date set, otherwise false
1075     */
1076    public function expires() { /* {{{ */
1077        if (intval($this->_expires) == 0)
1078            return false;
1079        else
1080            return true;
1081    } /* }}} */
1082
1083    /**
1084     * Get expiration time of document
1085     *
1086     * @return integer/boolean expiration date as unix timestamp or false
1087     */
1088    public function getExpires() { /* {{{ */
1089        if (intval($this->_expires) == 0)
1090            return false;
1091        else
1092            return $this->_expires;
1093    } /* }}} */
1094
1095    /**
1096     * Set expiration date as unix timestamp
1097     *
1098     * @param integer $expires unix timestamp of expiration date
1099     * @return bool
1100     */
1101    public function setExpires($expires) { /* {{{ */
1102        $db = $this->_dms->getDB();
1103
1104        $expires = (!$expires) ? 0 : $expires;
1105
1106        if ($expires == $this->_expires) {
1107            // No change is necessary.
1108            return true;
1109        }
1110
1111        $queryStr = "UPDATE `tblDocuments` SET `expires` = " . (int) $expires . " WHERE `id` = " . $this->_id;
1112        if (!$db->getResult($queryStr))
1113            return false;
1114
1115        $this->_expires = $expires;
1116        return true;
1117    } /* }}} */
1118
1119    /**
1120     * Check if the document has expired
1121     *
1122     * The method expects to database field 'expired' to hold the timestamp
1123     * of the start of day at which end the document expires. The document will
1124     * expire if that day is over. Hence, a document will *not* 
1125     * be expired during the day of expiration but at the end of that day
1126     *
1127     * @return boolean true if document has expired otherwise false
1128     */
1129    public function hasExpired() { /* {{{ */
1130        if (intval($this->_expires) == 0) return false;
1131        if (time()>=$this->_expires+24*60*60) return true;
1132        return false;
1133    } /* }}} */
1134
1135    /**
1136     * Check if the document has expired and set the status accordingly
1137     *
1138     * It will also recalculate the status if the current status is
1139     * set to S_EXPIRED but the document isn't actually expired.
1140     * The method will update the document status log database table
1141     * if needed.
1142     * FIXME: some left over reviewers/approvers are in the way if
1143     * no workflow is set and traditional workflow mode is on. In that
1144     * case the status is set to S_DRAFT_REV or S_DRAFT_APP
1145     *
1146     * @return boolean true if status has changed
1147     */
1148    public function verifyLastestContentExpriry(){ /* {{{ */
1149        $lc=$this->getLatestContent();
1150        if($lc) {
1151            $st=$lc->getStatus();
1152
1153            if (($st["status"]==S_DRAFT_REV || $st["status"]==S_DRAFT_APP || $st["status"]==S_IN_WORKFLOW || $st["status"]==S_RELEASED || $st["status"]==S_IN_REVISION) && $this->hasExpired()){
1154                return $lc->setStatus(S_EXPIRED,"", $this->getOwner());
1155            }
1156            elseif ($st["status"]==S_EXPIRED && !$this->hasExpired() ){
1157                $lc->verifyStatus(true, $this->getOwner());
1158                return true;
1159            }
1160        }
1161        return false;
1162    } /* }}} */
1163
1164    /**
1165     * Check if latest content of the document has a scheduled
1166     * revision workflow.
1167     *
1168     * This method was moved into SeedDMS_Core_DocumentContent and
1169     * the original method in SeedDMS_Core_Document now uses it for
1170     * the latest version.
1171     *
1172     * @param object $user user requesting the possible automatic change
1173     * @param string $next next date for review
1174     * @return boolean true if status has changed
1175     */
1176    function checkForDueRevisionWorkflow($user, $next=''){ /* {{{ */
1177        $lc=$this->getLatestContent();
1178        if($lc) {
1179            return $lc->checkForDueRevisionWorkflow($user, $next);
1180        }
1181        return false;
1182    } /* }}} */
1183
1184    /**
1185     * Check if document is locked
1186     *
1187     * @return boolean true if locked otherwise false
1188     */
1189    public function isLocked() { return $this->_locked != -1; }
1190
1191    /**
1192     * Lock or unlock document
1193     *
1194     * @param SeedDMS_Core_User|bool $falseOrUser user object for locking or false for unlocking
1195     * @return boolean true if operation was successful otherwise false
1196     */
1197    public function setLocked($falseOrUser) { /* {{{ */
1198        $db = $this->_dms->getDB();
1199
1200        $lockUserID = -1;
1201        if (is_bool($falseOrUser) && !$falseOrUser) {
1202            $queryStr = "DELETE FROM `tblDocumentLocks` WHERE `document` = ".$this->_id;
1203        }
1204        else if (is_object($falseOrUser)) {
1205            $queryStr = "INSERT INTO `tblDocumentLocks` (`document`, `userID`) VALUES (".$this->_id.", ".$falseOrUser->getID().")";
1206            $lockUserID = $falseOrUser->getID();
1207        }
1208        else {
1209            return false;
1210        }
1211        if (!$db->getResult($queryStr)) {
1212            return false;
1213        }
1214        unset($this->_lockingUser);
1215        $this->_locked = $lockUserID;
1216        return true;
1217    } /* }}} */
1218
1219    /**
1220     * Get the user currently locking the document
1221     *
1222     * @return SeedDMS_Core_User|bool user have a lock
1223     */
1224    public function getLockingUser() { /* {{{ */
1225        if (!$this->isLocked())
1226            return false;
1227
1228        if (!isset($this->_lockingUser))
1229            $this->_lockingUser = $this->_dms->getUser($this->_locked);
1230        return $this->_lockingUser;
1231    } /* }}} */
1232
1233    /**
1234     * Check if document is checked out
1235     *
1236     * @return boolean true if checked out otherwise false
1237     */
1238    function isCheckedOut() { /* {{{ */
1239        $db = $this->_dms->getDB();
1240
1241        $queryStr = "SELECT * FROM `tblDocumentCheckOuts` WHERE `document` = " . (int) $this->_id;
1242        $resArr = $db->getResultArray($queryStr);
1243        if ((is_bool($resArr) && $resArr==false) || (count($resArr)==0)) {
1244            // Could not find a check out for the selected document.
1245            return false;
1246        } else {
1247            // A check out has been identified for this document.
1248            return true;
1249        }
1250    } /* }}} */
1251
1252    /**
1253     * Get checkout info for document
1254     *
1255     * This returns the checkouts for a document. There could be several checkouts
1256     * for one document, but usually there is just one.
1257     *
1258     * @return array/boolean records from table tblDocumentCheckOuts or false
1259     * in case of an error.
1260     */
1261    function getCheckOutInfo() { /* {{{ */
1262        $db = $this->_dms->getDB();
1263
1264        $queryStr = "SELECT * FROM `tblDocumentCheckOuts` WHERE `document` = " . (int) $this->_id;
1265        $resArr = $db->getResultArray($queryStr);
1266        if ((is_bool($resArr) && $resArr==false) || (count($resArr)==0)) {
1267            // Could not find a check out for the selected document.
1268            return false;
1269        } else {
1270            // A check out has been identified for this document.
1271            return $resArr;
1272        }
1273    } /* }}} */
1274
1275
1276    /**
1277     * Check out document
1278     *
1279     * Creates a check out record for the document and copies the latest
1280     * version of the document into the given checkout dir.
1281     *
1282     * @param object $user object of user doing the checkout
1283     * @param string $checkoutdir directory where the file will be placed
1284     * @return object object of class SeedDMS_Core_DocumentCheckOut
1285     */
1286    function checkOut($user, $checkoutdir) { /* {{{ */
1287        $db = $this->_dms->getDB();
1288
1289        if(self::isCheckedOut())
1290            return false;
1291
1292        /* Check if checkout dir is writable */
1293        if(!file_exists($checkoutdir)) {
1294            return false;
1295        }
1296
1297        $db->startTransaction();
1298
1299        $lc = self::getLatestContent();
1300
1301        $ext = pathinfo($this->getName(), PATHINFO_EXTENSION);
1302        $oext = pathinfo($lc->getOriginalFileName(), PATHINFO_EXTENSION);
1303        if($ext == $oext)
1304            $filename = preg_replace('/[^A-Za-z0-9_.-]/', '_', $this->getName());
1305        else {
1306            $filename = preg_replace('/[^A-Za-z0-9_-]/', '_', $this->getName()).'.'.$oext;
1307        }
1308        $filename = $checkoutdir.$this->getID().'-'.$lc->getVersion().'-'.$filename; //$lc->getOriginalFileName();
1309        $queryStr = "INSERT INTO `tblDocumentCheckOuts` (`document`, `version`, `userID`, `date`, `filename`) VALUES (".$this->_id.", ".$lc->getVersion().", ".$user->getID().", ".$db->getCurrentDatetime().", ".$db->qstr($filename).")";
1310        if (!$db->getResult($queryStr))
1311            return false;
1312
1313        /* Try to copy the file */
1314        $err = SeedDMS_Core_File::copyFile($this->_dms->contentDir . $this->getDir() . $lc->getFileName(), $filename);
1315        if (!$err) {
1316            $db->rollbackTransaction();
1317            return false;
1318        }
1319
1320        $db->commitTransaction();
1321        return true;
1322    } /* }}} */
1323
1324    /**
1325     * Check in document
1326     *
1327     * Î¤his function is similar to SeedDMS_Core_Document::addContent()
1328     * but reads the content from the file which was previously checked out.
1329     * Internal this method calls
1330     * SeedDMS_Core_Document::addContent() but takes over the original
1331     * filename, filetype and mimetype from the checked out version.
1332     * No matter in which state the current checked out file is, the
1333     * document will be checked back in afterwards.
1334     *
1335     * There are various reason why a check in may fail. In those cases
1336     * this method will return false, but if the checked out document has
1337     * disappeared, the checkout will be ended and the method returns true
1338     * without creating a new version.
1339     *
1340     * The check in may not be done by the user who has done the check out,
1341     * but if it is a different user, this user must have unlimited access
1342     * on the document.
1343     *
1344     * @param string $comment
1345     * @param object $user
1346     * @param array $reviewers
1347     * @param array $approvers
1348     * @param integer $version
1349     * @param array $attributes
1350     * @param object $workflow
1351     * @param integer $initstate intial document status
1352     * @return boolean|object false in case of error, true if no error occurs but
1353     * the document remains unchanged (because the checked out file has not
1354     * changed or it has disappeared and couldnt't be checked in), or
1355     * an instance of class SeedDMS_Core_AddContentResultSet if the document
1356     * was updated.
1357     */
1358    function checkIn($comment, $user, $reviewers=array(), $approvers=array(), $version=0, $attributes=array(), $workflow=null, $initstate=S_RELEASED) { /* {{{ */
1359        $db = $this->_dms->getDB();
1360
1361        $infos = self::getCheckOutInfo();
1362        if(!$infos)
1363            return false;
1364        $info = $infos[0];
1365        $lc = self::getLatestContent();
1366
1367        /* If file doesn't exist anymore, then just remove the record from the db */
1368        if(!file_exists($info['filename'])) {
1369            $queryStr = "DELETE FROM `tblDocumentCheckOuts` WHERE `document` = ".$this->_id;
1370            $db->getResult($queryStr);
1371            return true;
1372        }
1373
1374        /* Check if version of checked out file is equal to current version */
1375        if($lc->getVersion() != $info['version']) {
1376            return false;
1377        }
1378
1379        /* Check if the user doing the check in is the same use as the one
1380         * have done the check out or at least have unlimited rights on the
1381         * document.
1382         */
1383        if($user->getID() != $info['userID'] && $this->getAccessMode($user) < M_ALL) {
1384            return false;
1385        }
1386
1387        $content = true;
1388        /* Do not create a new version if the file was unchanged */
1389        $checksum = SeedDMS_Core_File::checksum($info['filename']);
1390        if($checksum != $lc->getChecksum()) {
1391            $content = $this->addContent($comment, $user, $info['filename'], $lc->getOriginalFileName(), $lc->getFileType(), $lc->getMimeType(), $reviewers, $approvers, $version, $attributes, $workflow, $initstate);
1392            if($content) {
1393                if(!$this->_dms->forceRename) {
1394                    SeedDMS_Core_File::removeFile($info['filename']);
1395                }
1396                $queryStr = "DELETE FROM `tblDocumentCheckOuts` WHERE `document` = ".$this->_id;
1397                $db->getResult($queryStr);
1398                return $content;
1399            } else {
1400                return false;
1401            }
1402        } else {
1403            SeedDMS_Core_File::removeFile($info['filename']);
1404            $queryStr = "DELETE FROM `tblDocumentCheckOuts` WHERE `document` = ".$this->_id;
1405            $db->getResult($queryStr);
1406            return true;
1407        }
1408    } /* }}} */
1409
1410    /**
1411     * Cancel check out of document
1412     *
1413     * This function will cancel a check out in progress by removing
1414     * the check out record from the database and removing the file
1415     * from the check out folder.
1416     *
1417     * @return boolean true if cancelation was successful
1418     */
1419    function cancelCheckOut() { /* {{{ */
1420        $db = $this->_dms->getDB();
1421
1422        $infos = self::getCheckOutInfo();
1423        if($infos) {
1424            $info = $infos[0];
1425
1426            $db->startTransaction();
1427            $queryStr = "DELETE FROM `tblDocumentCheckOuts` WHERE `document` = ".$this->_id;
1428            if (!$db->getResult($queryStr)) {
1429                $db->rollbackTransaction();
1430                return false;
1431            }
1432            if(file_exists($info['filename']) && !SeedDMS_Core_File::removeFile($info['filename'])) {
1433                $db->rollbackTransaction();
1434                return false;
1435            }
1436            $db->commitTransaction();
1437        }
1438
1439        return true;
1440
1441    } /* }}} */
1442
1443    /**
1444     * Return the check out status of the document
1445     *
1446     * This method returns the checkout status of a previosly checked out
1447     * document. If a document was checked out more then once, the parameter
1448     * $index can be passed to retrieve a certain checkout info.
1449     *
1450     * @return int 1=The checked out file doesn't exists anymore,
1451     * 2=The checked out version doesn't exists anymore
1452     * 3=The checked out file has not been modified yet
1453     * 4=new check out record in database found
1454     * 0=The checked out file is modified and check in will create a new version
1455     */
1456    function checkOutStatus($index=0) { /* {{{ */
1457        $infos = self::getCheckOutInfo();
1458        if(!$infos || !isset($infos[$index]))
1459            return 4;
1460
1461        $info = $infos[$index];
1462        $lc = self::getLatestContent();
1463
1464        /* If file doesn't exist anymore, then just remove the record from the db */
1465        if(!file_exists($info['filename'])) {
1466            return 1;
1467        }
1468
1469        /* Check if version of checked out file is equal to current version */
1470        if($lc->getVersion() != $info['version']) {
1471            return 2;
1472        }
1473
1474        $checksum = SeedDMS_Core_File::checksum($info['filename']);
1475        if($checksum == $lc->getChecksum()) {
1476            return 3;
1477        }
1478
1479        return 0;
1480    } /* }}} */
1481
1482    /**
1483     * @return float
1484     */
1485    public function getSequence() { return $this->_sequence; }
1486
1487    /**
1488     * @param float $seq
1489     * @return bool
1490     */
1491    public function setSequence($seq) { /* {{{ */
1492        $db = $this->_dms->getDB();
1493
1494        $queryStr = "UPDATE `tblDocuments` SET `sequence` = " . $seq . " WHERE `id` = " . $this->_id;
1495        if (!$db->getResult($queryStr))
1496            return false;
1497
1498        $this->_sequence = $seq;
1499        return true;
1500    } /* }}} */
1501
1502    /**
1503     * Delete all entries for this document from the access control list
1504     *
1505     * @param boolean $noclean set to true if notifier list shall not be clean up
1506     * @return boolean true if operation was successful otherwise false
1507     */
1508    public function clearAccessList($noclean=false) { /* {{{ */
1509        $db = $this->_dms->getDB();
1510
1511        $queryStr = "DELETE FROM `tblACLs` WHERE `targetType` = " . T_DOCUMENT . " AND `target` = " . $this->_id;
1512        if (!$db->getResult($queryStr))
1513            return false;
1514
1515        unset($this->_accessList);
1516        $this->_readAccessList = array();
1517
1518        if(!$noclean)
1519            $this->cleanNotifyList();
1520
1521        return true;
1522    } /* }}} */
1523
1524    /**
1525     * Returns a list of access privileges
1526     *
1527     * If the document inherits the access privileges from the parent folder
1528     * those will be returned.
1529     * $mode and $op can be set to restrict the list of returned access
1530     * privileges. If $mode is set to M_ANY no restriction will apply
1531     * regardless of the value of $op. The returned array contains a list
1532     * of {@see SeedDMS_Core_UserAccess} and
1533     * {@see SeedDMS_Core_GroupAccess} objects. Even if the document
1534     * has no access list the returned array contains the two elements
1535     * 'users' and 'groups' which are than empty. The methode returns false
1536     * if the function fails.
1537     *
1538     * @param int $mode access mode (defaults to M_ANY)
1539     * @param int|string $op operation (defaults to O_EQ)
1540     * @return bool|array
1541     */
1542    public function getAccessList($mode = M_ANY, $op = O_EQ) { /* {{{ */
1543        $db = $this->_dms->getDB();
1544
1545        if ($this->inheritsAccess()) {
1546            $res = $this->getFolder();
1547            if (!$res) return false;
1548            $pacl = $res->getAccessList($mode, $op);
1549            return $pacl;
1550        } else {
1551            $pacl = array("groups" => array(), "users" => array());
1552        }
1553
1554        if (!isset($this->_accessList[$mode])) {
1555            if ($op!=O_GTEQ && $op!=O_LTEQ && $op!=O_EQ) {
1556                return false;
1557            }
1558            $modeStr = "";
1559            if ($mode!=M_ANY) {
1560                $modeStr = " AND `mode`".$op.(int)$mode;
1561            }
1562            $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1563                " AND `target` = " . $this->_id . $modeStr . " ORDER BY `targetType`";
1564            $resArr = $db->getResultArray($queryStr);
1565            if (is_bool($resArr) && !$resArr)
1566                return false;
1567
1568            $this->_accessList[$mode] = array("groups" => array(), "users" => array());
1569            foreach ($resArr as $row) {
1570                if ($row["userID"] != -1)
1571                    array_push($this->_accessList[$mode]["users"], new SeedDMS_Core_UserAccess($this->_dms->getUser($row["userID"]), (int) $row["mode"]));
1572                else //if ($row["groupID"] != -1)
1573                    array_push($this->_accessList[$mode]["groups"], new SeedDMS_Core_GroupAccess($this->_dms->getGroup($row["groupID"]), (int) $row["mode"]));
1574            }
1575        }
1576
1577        return $this->_accessList[$mode];
1578        return SeedDMS_Core_DMS::mergeAccessLists($pacl, $this->_accessList[$mode]);
1579    } /* }}} */
1580
1581    /**
1582     * Add access right to document
1583     *
1584     * This method may change in the future. Instead of passing a flag
1585     * and a user/group id a user or group object will be expected.
1586     * Starting with version 5.1.25 this method will first check if there
1587     * is already an access right for the user/group.
1588     *
1589     * @param integer $mode access mode
1590     * @param integer $userOrGroupID id of user or group
1591     * @param integer $isUser set to 1 if $userOrGroupID is the id of a
1592     *        user otherwise it will be considered a group id
1593     * @return bool true on success, otherwise false
1594     */
1595    public function addAccess($mode, $userOrGroupID, $isUser) { /* {{{ */
1596        $db = $this->_dms->getDB();
1597
1598        if($mode < M_NONE || $mode > M_ALL)
1599            return false;
1600
1601        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1602
1603        /* Adding a second access right will return false */
1604        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1605                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ".$userOrGroupID;
1606        $resArr = $db->getResultArray($queryStr);
1607        if (is_bool($resArr) || $resArr)
1608            return false;
1609
1610        $queryStr = "INSERT INTO `tblACLs` (`target`, `targetType`, ".$userOrGroup.", `mode`) VALUES
1611                    (".$this->_id.", ".T_DOCUMENT.", " . (int) $userOrGroupID . ", " .(int) $mode. ")";
1612        if (!$db->getResult($queryStr))
1613            return false;
1614
1615        unset($this->_accessList);
1616        $this->_readAccessList = array();
1617
1618        // Update the notify list, if necessary.
1619        if ($mode == M_NONE) {
1620            $this->removeNotify($userOrGroupID, $isUser);
1621        }
1622
1623        return true;
1624    } /* }}} */
1625
1626    /**
1627     * Change access right of document
1628     *
1629     * This method may change in the future. Instead of passing a flag
1630     * and a user/group id a user or group object will be expected.
1631     *
1632     * @param integer $newMode access mode
1633     * @param integer $userOrGroupID id of user or group
1634     * @param integer $isUser set to 1 if $userOrGroupID is the id of a
1635     *        user otherwise it will be considered a group id
1636     * @return bool true on success, otherwise false
1637     */
1638    public function changeAccess($newMode, $userOrGroupID, $isUser) { /* {{{ */
1639        $db = $this->_dms->getDB();
1640
1641        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1642
1643        /* Get the old access right */
1644        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1645                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ". (int) $userOrGroupID;
1646        $resArr = $db->getResultArray($queryStr);
1647        if (!$resArr)
1648            return false;
1649
1650        $oldmode = $resArr[0]['mode'];
1651
1652        $queryStr = "UPDATE `tblACLs` SET `mode` = " . (int) $newMode . " WHERE `targetType` = ".T_DOCUMENT." AND `target` = " . $this->_id . " AND " . $userOrGroup . " = " . (int) $userOrGroupID;
1653        if (!$db->getResult($queryStr))
1654            return false;
1655
1656        unset($this->_accessList);
1657        $this->_readAccessList = array();
1658
1659        // Update the notify list, if necessary.
1660        if ($newMode == M_NONE) {
1661            $this->removeNotify($userOrGroupID, $isUser);
1662        }
1663
1664        return $oldmode;
1665    } /* }}} */
1666
1667    /**
1668     * Remove access rights for a user or group
1669     *
1670     * @param integer $userOrGroupID ID of user or group
1671     * @param boolean $isUser true if $userOrGroupID is a user id, false if it
1672     *        is a group id.
1673     * @return boolean true on success, otherwise false
1674     */
1675    public function removeAccess($userOrGroupID, $isUser) { /* {{{ */
1676        $db = $this->_dms->getDB();
1677
1678        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
1679
1680        /* Get the old access right */
1681        $queryStr = "SELECT * FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT.
1682                " AND `target` = " . $this->_id . " AND ". $userOrGroup . " = ". (int) $userOrGroupID;
1683        $resArr = $db->getResultArray($queryStr);
1684        if (!$resArr)
1685            return false;
1686
1687        $queryStr = "DELETE FROM `tblACLs` WHERE `targetType` = ".T_DOCUMENT." AND `target` = ".$this->_id." AND ".$userOrGroup." = " . (int) $userOrGroupID;
1688        if (!$db->getResult($queryStr))
1689            return false;
1690
1691        unset($this->_accessList);
1692        $this->_readAccessList = array();
1693
1694        // Update the notify list, if the user looses access rights.
1695        $mode = ($isUser ? $this->getAccessMode($this->_dms->getUser($userOrGroupID)) : $this->getGroupAccessMode($this->_dms->getGroup($userOrGroupID)));
1696        if ($mode == M_NONE) {
1697            $this->removeNotify($userOrGroupID, $isUser);
1698        }
1699
1700        return true;
1701    } /* }}} */
1702
1703    /**
1704     * Returns the greatest access privilege for a given user
1705     *
1706     * This method returns the access mode for a given user. An administrator
1707     * and the owner of the folder has unrestricted access. A guest user has
1708     * read only access or no access if access rights are further limited
1709     * by access control lists. All other users have access rights according
1710     * to the access control lists or the default access. This method will
1711     * recursive check for access rights of parent folders if access rights
1712     * are inherited.
1713     *
1714     * The function searches the access control list for entries of
1715     * user $user. If it finds more than one entry it will return the
1716     * one allowing the greatest privileges, but user rights will always
1717     * precede group rights. If there is no entry in the
1718     * access control list, it will return the default access mode.
1719     * The function takes inherited access rights into account.
1720     * For a list of possible access rights see @file inc.AccessUtils.php
1721     *
1722     * Having access on a document does not necessarily mean the document
1723     * content is accessible too. Accessing the content is checked by
1724     * {@see SeedDMS_Core_DocumentContent::getAccessMode()} which calls
1725     * a callback function defined by the application. If the callback
1726     * function is not set, access on the content is always granted.
1727     *
1728     * Before checking the access in the method itself a callback 'onCheckAccessDocument'
1729     * is called. If it returns a value > 0, then this will be returned by this
1730     * method without any further checks. The optional paramater $context
1731     * will be passed as a third parameter to the callback. It contains
1732     * the operation for which the access mode is retrieved. It is for example
1733     * set to 'removeDocument' if the access mode is used to check for sufficient
1734     * permission on deleting a document.
1735     *
1736     * @param $user object instance of class SeedDMS_Core_User
1737     * @param string $context context in which the access mode is requested
1738     * @return integer access mode
1739     */
1740    public function getAccessMode($user, $context='') { /* {{{ */
1741        if(!$user)
1742            return M_NONE;
1743
1744        /* Check if 'onCheckAccessDocument' callback is set */
1745        if(isset($this->_dms->callbacks['onCheckAccessDocument'])) {
1746            foreach($this->_dms->callbacks['onCheckAccessDocument'] as $callback) {
1747                if(($ret = call_user_func($callback[0], $callback[1], $this, $user, $context)) > 0) {
1748                    return $ret;
1749                }
1750            }
1751        }
1752
1753        /* Administrators have unrestricted access */
1754        if ($user->isAdmin()) return M_ALL;
1755
1756        /* The owner of the document has unrestricted access */
1757        if ($user->getID() == $this->_ownerID) return M_ALL;
1758
1759        /* Check ACLs */
1760        $accessList = $this->getAccessList();
1761        if (!$accessList) return false;
1762
1763        /** @var SeedDMS_Core_UserAccess $userAccess */
1764        foreach ($accessList["users"] as $userAccess) {
1765            if ($userAccess->getUserID() == $user->getID()) {
1766                $mode = $userAccess->getMode();
1767                if ($user->isGuest()) {
1768                    if ($mode >= M_READ) $mode = M_READ;
1769                }
1770                return $mode;
1771            }
1772        }
1773
1774        /* Get the highest right defined by a group */
1775        if($accessList['groups']) {
1776            $mode = 0;
1777            /** @var SeedDMS_Core_GroupAccess $groupAccess */
1778            foreach ($accessList["groups"] as $groupAccess) {
1779                if ($user->isMemberOfGroup($groupAccess->getGroup())) {
1780                    if ($groupAccess->getMode() > $mode)
1781                        $mode = $groupAccess->getMode();
1782                }
1783            }
1784            if($mode) {
1785                if ($user->isGuest()) {
1786                    if ($mode >= M_READ) $mode = M_READ;
1787                }
1788                return $mode;
1789            }
1790        }
1791
1792        $mode = $this->getDefaultAccess();
1793        if ($user->isGuest()) {
1794            if ($mode >= M_READ) $mode = M_READ;
1795        }
1796        return $mode;
1797    } /* }}} */
1798
1799    /**
1800     * Returns the greatest access privilege for a given group
1801     *
1802     * This method searches the access control list for entries of
1803     * group $group. If it finds more than one entry it will return the
1804     * one allowing the greatest privileges. If there is no entry in the
1805     * access control list, it will return the default access mode.
1806     * The function takes inherited access rights into account.
1807     * For a list of possible access rights see @file inc.AccessUtils.php
1808     *
1809     * @param SeedDMS_Core_Group $group object instance of class SeedDMS_Core_Group
1810     * @return integer access mode
1811     */
1812    public function getGroupAccessMode($group) { /* {{{ */
1813        $highestPrivileged = M_NONE;
1814
1815        //ACLs durchforsten
1816        $foundInACL = false;
1817        $accessList = $this->getAccessList();
1818        if (!$accessList)
1819            return false;
1820
1821        /** @var SeedDMS_Core_GroupAccess $groupAccess */
1822        foreach ($accessList["groups"] as $groupAccess) {
1823            if ($groupAccess->getGroupID() == $group->getID()) {
1824                $foundInACL = true;
1825                if ($groupAccess->getMode() > $highestPrivileged)
1826                    $highestPrivileged = $groupAccess->getMode();
1827                if ($highestPrivileged == M_ALL) // max access right -> skip the rest
1828                    return $highestPrivileged;
1829            }
1830        }
1831
1832        if ($foundInACL)
1833            return $highestPrivileged;
1834
1835        //Standard-Berechtigung verwenden
1836        return $this->getDefaultAccess();
1837    } /* }}} */
1838
1839    /**
1840     * Returns a list of all notifications
1841     *
1842     * The returned list has two elements called 'users' and 'groups'. Each one
1843     * is an array itself countaining objects of class SeedDMS_Core_User and
1844     * SeedDMS_Core_Group.
1845     *
1846     * @param integer $type type of notification (not yet used)
1847     * @param bool $incdisabled set to true if disabled user shall be included
1848     * @return array|bool
1849     */
1850    public function getNotifyList($type=0, $incdisabled=false) { /* {{{ */
1851        if (empty($this->_notifyList)) {
1852            $db = $this->_dms->getDB();
1853
1854            $queryStr ="SELECT * FROM `tblNotify` WHERE `targetType` = " . T_DOCUMENT . " AND `target` = " . $this->_id;
1855            $resArr = $db->getResultArray($queryStr);
1856            if (is_bool($resArr) && $resArr == false)
1857                return false;
1858
1859            $this->_notifyList = array("groups" => array(), "users" => array());
1860            foreach ($resArr as $row)
1861            {
1862                if ($row["userID"] != -1) {
1863                    $u = $this->_dms->getUser($row["userID"]);
1864                    if($u && (!$u->isDisabled() || $incdisabled))
1865                        array_push($this->_notifyList["users"], $u);
1866                } else { //if ($row["groupID"] != -1)
1867                    $g = $this->_dms->getGroup($row["groupID"]);
1868                    if($g)
1869                        array_push($this->_notifyList["groups"], $g);
1870                }
1871            }
1872        }
1873        return $this->_notifyList;
1874    } /* }}} */
1875
1876    /**
1877     * Make sure only users/groups with read access are in the notify list
1878     *
1879     */
1880    public function cleanNotifyList() { /* {{{ */
1881        // If any of the notification subscribers no longer have read access,
1882        // remove their subscription.
1883        if (empty($this->_notifyList))
1884            $this->getNotifyList();
1885
1886        /* Make a copy of both notifier lists because removeNotify will empty
1887         * $this->_notifyList and the second foreach will not work anymore.
1888         */
1889        /** @var SeedDMS_Core_User[] $nusers */
1890        $nusers = $this->_notifyList["users"];
1891        /** @var SeedDMS_Core_Group[] $ngroups */
1892        $ngroups = $this->_notifyList["groups"];
1893        foreach ($nusers as $u) {
1894            if ($this->getAccessMode($u) < M_READ) {
1895                $this->removeNotify($u->getID(), true);
1896            }
1897        }
1898        foreach ($ngroups as $g) {
1899            if ($this->getGroupAccessMode($g) < M_READ) {
1900                $this->removeNotify($g->getID(), false);
1901            }
1902        }
1903    } /* }}} */
1904
1905    /**
1906     * Add a user/group to the notification list
1907     *
1908     * This method does not check if the currently logged in user
1909     * is allowed to add a notification. This must be checked by the calling
1910     * application.
1911     *
1912     * @param $userOrGroupID integer id of user or group to add
1913     * @param $isUser integer 1 if $userOrGroupID is a user,
1914     *                0 if $userOrGroupID is a group
1915     * @return integer  0: Update successful.
1916     *                 -1: Invalid User/Group ID.
1917     *                 -2: Target User / Group does not have read access.
1918     *                 -3: User is already subscribed.
1919     *                 -4: Database / internal error.
1920     */
1921    public function addNotify($userOrGroupID, $isUser) { /* {{{ */
1922        $db = $this->_dms->getDB();
1923
1924        $userOrGroup = ($isUser ? "`userID`" : "`groupID`");
1925
1926        /* Verify that user / group exists. */
1927        $obj = ($isUser ? $this->_dms->getUser($userOrGroupID) : $this->_dms->getGroup($userOrGroupID));
1928        if (!is_object($obj)) {
1929            return -1;
1930        }
1931
1932        /* Verify that the requesting user has permission to add the target to
1933         * the notification system.
1934         */
1935        /*
1936         * The calling application should enforce the policy on who is allowed
1937         * to add someone to the notification system. If is shall remain here
1938         * the currently logged in user should be passed to this function
1939         *
1940        GLOBAL $user;
1941        if ($user->isGuest()) {
1942            return -2;
1943        }
1944        if (!$user->isAdmin()) {
1945            if ($isUser) {
1946                if ($user->getID() != $obj->getID()) {
1947                    return -2;
1948                }
1949            }
1950            else {
1951                if (!$obj->isMember($user)) {
1952                    return -2;
1953                }
1954            }
1955        }
1956         */
1957
1958        /* Verify that target user / group has read access to the document. */
1959        if ($isUser) {
1960            // Users are straightforward to check.
1961            if ($this->getAccessMode($obj) < M_READ) {
1962                return -2;
1963            }
1964        }
1965        else {
1966            // Groups are a little more complex.
1967            if ($this->getDefaultAccess() >= M_READ) {
1968                // If the default access is at least READ-ONLY, then just make sure
1969                // that the current group has not been explicitly excluded.
1970                $acl = $this->getAccessList(M_NONE, O_EQ);
1971                $found = false;
1972                /** @var SeedDMS_Core_GroupAccess $group */
1973                foreach ($acl["groups"] as $group) {
1974                    if ($group->getGroupID() == $userOrGroupID) {
1975                        $found = true;
1976                        break;
1977                    }
1978                }
1979                if ($found) {
1980                    return -2;
1981                }
1982            }
1983            else {
1984                // The default access is restricted. Make sure that the group has
1985                // been explicitly allocated access to the document.
1986                $acl = $this->getAccessList(M_READ, O_GTEQ);
1987                if (is_bool($acl)) {
1988                    return -4;
1989                }
1990                $found = false;
1991                /** @var SeedDMS_Core_GroupAccess $group */
1992                foreach ($acl["groups"] as $group) {
1993                    if ($group->getGroupID() == $userOrGroupID) {
1994                        $found = true;
1995                        break;
1996                    }
1997                }
1998                if (!$found) {
1999                    return -2;
2000                }
2001            }
2002        }
2003        /* Check to see if user/group is already on the list. */
2004        $queryStr = "SELECT * FROM `tblNotify` WHERE `tblNotify`.`target` = '".$this->_id."' ".
2005            "AND `tblNotify`.`targetType` = '".T_DOCUMENT."' ".
2006            "AND `tblNotify`.".$userOrGroup." = '".(int) $userOrGroupID."'";
2007        $resArr = $db->getResultArray($queryStr);
2008        if (is_bool($resArr)) {
2009            return -4;
2010        }
2011        if (count($resArr)>0) {
2012            return -3;
2013        }
2014
2015        $queryStr = "INSERT INTO `tblNotify` (`target`, `targetType`, " . $userOrGroup . ") VALUES (" . $this->_id . ", " . T_DOCUMENT . ", " . (int) $userOrGroupID . ")";
2016        if (!$db->getResult($queryStr))
2017            return -4;
2018
2019        unset($this->_notifyList);
2020        return 0;
2021    } /* }}} */
2022
2023    /**
2024     * Remove a user or group from the notification list
2025     *
2026     * This method does not check if the currently logged in user
2027     * is allowed to remove a notification. This must be checked by the calling
2028     * application.
2029     *
2030     * @param integer $userOrGroupID id of user or group
2031     * @param boolean $isUser boolean true if a user is passed in $userOrGroupID, false
2032     *        if a group is passed in $userOrGroupID
2033     * @param integer $type type of notification (0 will delete all) Not used yet!
2034     * @return integer 0 if operation was succesful
2035     *                 -1 if the userid/groupid is invalid
2036     *                 -3 if the user/group is already subscribed
2037     *                 -4 in case of an internal database error
2038     */
2039    public function removeNotify($userOrGroupID, $isUser, $type=0) { /* {{{ */
2040        $db = $this->_dms->getDB();
2041
2042        /* Verify that user / group exists. */
2043        /** @var SeedDMS_Core_Group|SeedDMS_Core_User $obj */
2044        $obj = ($isUser ? $this->_dms->getUser($userOrGroupID) : $this->_dms->getGroup($userOrGroupID));
2045        if (!is_object($obj)) {
2046            return -1;
2047        }
2048
2049        $userOrGroup = ($isUser) ? "`userID`" : "`groupID`";
2050
2051        /* Verify that the requesting user has permission to add the target to
2052         * the notification system.
2053         */
2054        /*
2055         * The calling application should enforce the policy on who is allowed
2056         * to add someone to the notification system. If is shall remain here
2057         * the currently logged in user should be passed to this function
2058         *
2059        GLOBAL $user;
2060        if ($user->isGuest()) {
2061            return -2;
2062        }
2063        if (!$user->isAdmin()) {
2064            if ($isUser) {
2065                if ($user->getID() != $obj->getID()) {
2066                    return -2;
2067                }
2068            }
2069            else {
2070                if (!$obj->isMember($user)) {
2071                    return -2;
2072                }
2073            }
2074        }
2075         */
2076
2077        /* Check to see if the target is in the database. */
2078        $queryStr = "SELECT * FROM `tblNotify` WHERE `tblNotify`.`target` = '".$this->_id."' ".
2079            "AND `tblNotify`.`targetType` = '".T_DOCUMENT."' ".
2080            "AND `tblNotify`.".$userOrGroup." = '".(int) $userOrGroupID."'";
2081        $resArr = $db->getResultArray($queryStr);
2082        if (is_bool($resArr)) {
2083            return -4;
2084        }
2085        if (count($resArr)==0) {
2086            return -3;
2087        }
2088
2089        $queryStr = "DELETE FROM `tblNotify` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT . " AND " . $userOrGroup . " = " . (int) $userOrGroupID;
2090        /* If type is given then delete only those notifications */
2091        if($type)
2092            $queryStr .= " AND `type` = ".(int) $type;
2093        if (!$db->getResult($queryStr))
2094            return -4;
2095
2096        unset($this->_notifyList);
2097        return 0;
2098    } /* }}} */
2099
2100    /**
2101     * Add content to a document
2102     *
2103     * Each document may have any number of content elements attached to it.
2104     * Each content element has a version number. Newer versions (greater
2105     * version number) replace older versions.
2106     *
2107     * @param string $comment comment
2108     * @param object $user user who shall be the owner of this content
2109     * @param string $tmpFile file containing the actuall content
2110     * @param string $orgFileName original file name
2111     * @param string $fileType
2112     * @param string $mimeType MimeType of the content
2113     * @param array $reviewers list of reviewers
2114     * @param array $approvers list of approvers
2115     * @param integer $version version number of content or 0 if next higher version shall be used.
2116     * @param array $attributes list of version attributes. The element key
2117     *        must be the id of the attribute definition.
2118     * @param object $workflow
2119     * @param integer $initstate intial document status
2120     * @return bool|SeedDMS_Core_AddContentResultSet
2121     */
2122    function addContent($comment, $user, $tmpFile, $orgFileName, $fileType, $mimeType, $reviewers=array(), $approvers=array(), $version=0, $attributes=array(), $workflow=null, $initstate=S_RELEASED) { /* {{{ */
2123        $db = $this->_dms->getDB();
2124
2125        // the doc path is id/version.filetype
2126        $dir = $this->getDir();
2127
2128        /* The version field in table tblDocumentContent used to be auto
2129         * increment but that requires the field to be primary as well if
2130         * innodb is used. That's why the version is now determined here.
2131         */
2132        if ((int)$version<1) {
2133            $queryStr = "SELECT MAX(`version`) AS m FROM `tblDocumentContent` WHERE `document` = ".$this->_id;
2134            $resArr = $db->getResultArray($queryStr);
2135            if (is_bool($resArr) && !$resArr)
2136                return false;
2137
2138            $version = $resArr[0]['m']+1;
2139        }
2140
2141        if($fileType == '.')
2142            $fileType = '';
2143        $filesize = SeedDMS_Core_File::fileSize($tmpFile);
2144        $checksum = SeedDMS_Core_File::checksum($tmpFile);
2145
2146        $db->startTransaction();
2147        $queryStr = "INSERT INTO `tblDocumentContent` (`document`, `version`, `comment`, `date`, `createdBy`, `dir`, `orgFileName`, `fileType`, `mimeType`, `fileSize`, `checksum`) VALUES ".
2148                        "(".$this->_id.", ".(int)$version.",".$db->qstr($comment).", ".$db->getCurrentTimestamp().", ".$user->getID().", ".$db->qstr($dir).", ".$db->qstr($orgFileName).", ".$db->qstr($fileType).", ".$db->qstr($mimeType).", ".$filesize.", ".$db->qstr($checksum).")";
2149        if (!$db->getResult($queryStr)) {
2150            $db->rollbackTransaction();
2151            return false;
2152        }
2153
2154        $contentID = $db->getInsertID('tblDocumentContent');
2155        $content = $this->getLatestContent($contentID);
2156
2157        if($storage = $this->_dms->getStorage()) {
2158            if(!$storage->saveContent($this, $content, $tmpFile)) {
2159                $db->rollbackTransaction();
2160                return false;
2161            }
2162        } else {
2163        // copy file
2164        if (!SeedDMS_Core_File::makeDir($this->_dms->contentDir . $dir)) {
2165            $db->rollbackTransaction();
2166            return false;
2167        }
2168        if($this->_dms->forceRename)
2169            $err = SeedDMS_Core_File::renameFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType);
2170        elseif($this->_dms->forceLink)
2171            $err = SeedDMS_Core_File::linkFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType);
2172        else
2173            $err = SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType);
2174        if (!$err) {
2175            $db->rollbackTransaction();
2176            return false;
2177        }
2178        }
2179
2180        $this->_content = null;
2181        $this->_latestContent = null;
2182        $docResultSet = new SeedDMS_Core_AddContentResultSet($content);
2183        $docResultSet->setDMS($this->_dms);
2184
2185        if($attributes) {
2186            foreach($attributes as $attrdefid=>$attribute) {
2187                /* $attribute can be a string or an array */
2188                if($attribute) {
2189                    if($attrdef = $this->_dms->getAttributeDefinition($attrdefid)) {
2190                        if(!$content->setAttributeValue($attrdef, $attribute)) {
2191                            $this->_removeContent($content);
2192                            $db->rollbackTransaction();
2193                            return false;
2194                        }
2195                    } else {
2196                        $this->_removeContent($content);
2197                        $db->rollbackTransaction();
2198                        return false;
2199                    }
2200                }
2201            }
2202        }
2203
2204        $queryStr = "INSERT INTO `tblDocumentStatus` (`documentID`, `version`) ".
2205            "VALUES (". $this->_id .", ". (int) $version .")";
2206        if (!$db->getResult($queryStr)) {
2207            $this->_removeContent($content);
2208            $db->rollbackTransaction();
2209            return false;
2210        }
2211
2212        $statusID = $db->getInsertID('tblDocumentStatus', 'statusID');
2213
2214        if($workflow)
2215            $content->setWorkflow($workflow, $user);
2216
2217        // Add reviewers into the database. Reviewers must review the document
2218        // and submit comments, if appropriate. Reviewers can also recommend that
2219        // a document be rejected.
2220        $pendingReview=false;
2221        /** @noinspection PhpUnusedLocalVariableInspection */
2222        foreach (array("i", "g") as $i){
2223            if (isset($reviewers[$i])) {
2224                foreach ($reviewers[$i] as $reviewerID) {
2225                    $reviewer=($i=="i" ?$this->_dms->getUser($reviewerID) : $this->_dms->getGroup($reviewerID));
2226                    $res = ($i=="i" ? $docResultSet->getContent()->addIndReviewer($reviewer, $user, true) : $docResultSet->getContent()->addGrpReviewer($reviewer, $user, true));
2227                    $docResultSet->addReviewer($reviewer, $i, $res);
2228                    // If no error is returned, or if the error is just due to email
2229                    // failure, mark the state as "pending review".
2230                    // FIXME: There seems to be no error code -4 anymore
2231                    if ($res==0 || $res=-3 || $res=-4) {
2232                        $pendingReview=true;
2233                    }
2234                }
2235            }
2236        }
2237        // Add approvers to the database. Approvers must also review the document
2238        // and make a recommendation on its release as an approved version.
2239        $pendingApproval=false;
2240        /** @noinspection PhpUnusedLocalVariableInspection */
2241        foreach (array("i", "g") as $i){
2242            if (isset($approvers[$i])) {
2243                foreach ($approvers[$i] as $approverID) {
2244                    $approver=($i=="i" ? $this->_dms->getUser($approverID) : $this->_dms->getGroup($approverID));
2245                    $res=($i=="i" ? $docResultSet->getContent()->addIndApprover($approver, $user, true) : $docResultSet->getContent()->addGrpApprover($approver, $user, !$pendingReview));
2246                    $docResultSet->addApprover($approver, $i, $res);
2247                    // FIXME: There seems to be no error code -4 anymore
2248                    if ($res==0 || $res=-3 || $res=-4) {
2249                        $pendingApproval=true;
2250                    }
2251                }
2252            }
2253        }
2254
2255        // If there are no reviewers or approvers, the document is automatically
2256        // promoted to the released state.
2257        if ($pendingReview) {
2258            $status = S_DRAFT_REV;
2259            $comment = "";
2260        }
2261        elseif ($pendingApproval) {
2262            $status = S_DRAFT_APP;
2263            $comment = "";
2264        }
2265        elseif($workflow) {
2266            $status = S_IN_WORKFLOW;
2267            $comment = ", workflow: ".$workflow->getName();
2268        } elseif($initstate == S_DRAFT) {
2269            $status = $initstate;
2270            $comment = "";
2271        } else {
2272            $status = S_RELEASED;
2273            $comment = "";
2274        }
2275        $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ".
2276            "VALUES ('". $statusID ."', '". $status."', 'New document content submitted". $comment ."', ".$db->getCurrentDatetime().", '". $user->getID() ."')";
2277        if (!$db->getResult($queryStr)) {
2278            $db->rollbackTransaction();
2279            return false;
2280        }
2281
2282        /** @noinspection PhpMethodParametersCountMismatchInspection */
2283        $docResultSet->setStatus($status);
2284
2285        $db->commitTransaction();
2286        return $docResultSet;
2287    } /* }}} */
2288
2289    /**
2290     * Replace a version of a document
2291     *
2292     * Each document may have any number of content elements attached to it.
2293     * This method replaces the file content of a given version.
2294     * Using this function is highly discourage, because it undermines the
2295     * idea of keeping all versions of a document as originally saved.
2296     * Content will only be replaced if the mimetype, filetype, user and
2297     * original filename are identical to the version being updated.
2298     *
2299     * This method was introduced for the webdav server because any saving
2300     * of a document created a new version.
2301     *
2302     * @param object $user user who shall be the owner of this content
2303     * @param string $tmpFile file containing the actuall content
2304     * @param string $orgFileName original file name
2305     * @param string $fileType
2306     * @param string $mimeType MimeType of the content
2307     * @param integer $version version number of content or 0 if latest version shall be replaced.
2308     * @return bool/array false in case of an error or a result set
2309     */
2310    public function replaceContent($version, $user, $tmpFile, $orgFileName, $fileType, $mimeType, $allowoverride=[]) { /* {{{ */
2311        $db = $this->_dms->getDB();
2312
2313        // the doc path is id/version.filetype
2314        $dir = $this->getDir();
2315
2316        /* If $version < 1 than replace the content of the latest version.
2317         */
2318        if ((int) $version<1) {
2319            $queryStr = "SELECT MAX(`version`) AS m FROM `tblDocumentContent` WHERE `document` = ".$this->_id;
2320            $resArr = $db->getResultArray($queryStr);
2321            if (is_bool($resArr) && !$resArr)
2322                return false;
2323
2324            $version = $resArr[0]['m'];
2325        }
2326
2327        $content = $this->getContentByVersion($version);
2328        if(!$content)
2329            return false;
2330
2331        if($fileType == '.')
2332            $fileType = '';
2333
2334        $sql = [];
2335        /* Check if $user, $orgFileName, $fileType and $mimeType are the same */
2336        if($user->getID() != $content->getUser()->getID()) {
2337            if(!empty($allowoverride['user']))
2338                $sql[] = "`createdBy`=".$user->getID();
2339            else
2340                return false;
2341        }
2342        if($orgFileName != $content->getOriginalFileName()) {
2343            if(!empty($allowoverride['orgfilename']))
2344                $sql[] = "`orgFileName`=".$db->qstr($orgFileName);
2345            else
2346                return false;
2347        }
2348        if($fileType != $content->getFileType()) {
2349            if(!empty($allowoverride['filetype']))
2350                $sql[] = "`fileType`=".$db->qstr($fileType);
2351            else
2352                return false;
2353        }
2354        if($mimeType != $content->getMimeType()) {
2355            if(!empty($allowoverride['mimetype']))
2356                $sql[] = "`mimeType`=".$db->qstr($mimeType);
2357            else
2358                return false;
2359        }
2360
2361        $filesize = SeedDMS_Core_File::fileSize($tmpFile);
2362        $checksum = SeedDMS_Core_File::checksum($tmpFile);
2363
2364        $db->startTransaction();
2365        $sql[] = "`date`=".$db->getCurrentTimestamp();
2366        $sql[] = "`fileSize`=".$filesize;
2367        $sql[] = "`checksum`=".$db->qstr($checksum);
2368        $queryStr = "UPDATE `tblDocumentContent` set ".implode(", ", $sql)." WHERE `id`=".$content->getID();
2369        if (!$db->getResult($queryStr)) {
2370            $db->rollbackTransaction();
2371            return false;
2372        }
2373
2374        if($storage = $this->_dms->getStorage()) {
2375            if(!$storage->replaceContent($this, $content, $tmpFile)) {
2376                $db->rollbackTransaction();
2377                return false;
2378            }
2379        } else {
2380        // copy file
2381            if (!SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $dir . $version . $fileType)) {
2382                $db->rollbackTransaction();
2383                return false;
2384            }
2385        }
2386
2387        $this->_content = null;
2388        $this->_latestContent = null;
2389        $db->commitTransaction();
2390
2391        return true;
2392    } /* }}} */
2393
2394    /**
2395     * Return all content elements of a document
2396     *
2397     * This method returns an array of content elements ordered by version.
2398     * Version which are not accessible because of its status, will be filtered
2399     * out. Access rights based on the document status are calculated for the
2400     * currently logged in user.
2401     *
2402     * @return bool|SeedDMS_Core_DocumentContent[]
2403     */
2404    public function getContent() { /* {{{ */
2405        $db = $this->_dms->getDB();
2406
2407        if (!isset($this->_content)) {
2408            $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version`";
2409            $resArr = $db->getResultArray($queryStr);
2410            if (is_bool($resArr) && !$resArr)
2411                return false;
2412
2413            $this->_content = array();
2414            $classname = $this->_dms->getClassname('documentcontent');
2415            $user = $this->_dms->getLoggedInUser();
2416            foreach ($resArr as $row) {
2417                /** @var SeedDMS_Core_DocumentContent $content */
2418                $content = new $classname($row["id"], $this, $row["version"], $row["comment"], $row["date"], $row["createdBy"], $row["dir"], $row["orgFileName"], $row["fileType"], $row["mimeType"], $row['fileSize'], $row['checksum'], $row['revisiondate']);
2419                /* TODO: Better use content id as key in $this->_content. This
2420                 * would allow to remove a single content object in removeContent().
2421                 * Currently removeContent() must clear $this->_content completely
2422                 */
2423                if($user) {
2424                    if($content->getAccessMode($user) >= M_READ)
2425                        array_push($this->_content, $content);
2426                } else {
2427                    array_push($this->_content, $content);
2428                }
2429            }
2430        }
2431
2432        return $this->_content;
2433    } /* }}} */
2434
2435    /**
2436     * Return the content element of a document with a given version number
2437     *
2438     * This method will check if the version is accessible and return false
2439     * if not. Access rights based on the document status are calculated for the
2440     * currently logged in user.
2441     *
2442     * @param integer $version version number of content element
2443     * @return SeedDMS_Core_DocumentContent|null|boolean object of class
2444     * {@see SeedDMS_Core_DocumentContent}, null if not content was found,
2445     * false in case of an error
2446     */
2447    public function getContentByVersion($version) { /* {{{ */
2448        if (!is_numeric($version)) return false;
2449
2450        if (isset($this->_content)) {
2451            foreach ($this->_content as $revision) {
2452                if ($revision->getVersion() == $version)
2453                    return $revision;
2454            }
2455            return null;
2456        }
2457
2458        $db = $this->_dms->getDB();
2459        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." AND `version` = " . (int) $version;
2460        $resArr = $db->getResultArray($queryStr);
2461        if (is_bool($resArr) && !$resArr)
2462            return false;
2463        if (count($resArr) != 1)
2464            return null;
2465
2466        $resArr = $resArr[0];
2467        $classname = $this->_dms->getClassname('documentcontent');
2468        /** @var SeedDMS_Core_DocumentContent $content */
2469        if($content = new $classname($resArr["id"], $this, $resArr["version"], $resArr["comment"], $resArr["date"], $resArr["createdBy"], $resArr["dir"], $resArr["orgFileName"], $resArr["fileType"], $resArr["mimeType"], $resArr['fileSize'], $resArr['checksum'], $resArr['revisiondate'])) {
2470            $user = $this->_dms->getLoggedInUser();
2471            /* A user with write access on the document may always see the version */
2472            if($user && $content->getAccessMode($user) == M_NONE)
2473                return null;
2474            else
2475                return $content;
2476        } else {
2477            return false;
2478        }
2479    } /* }}} */
2480
2481    /**
2482     * Check if a given version is the latest version of the document
2483     *
2484     * @param integer $version version number of content element
2485     * @return SeedDMS_Core_DocumentContent|boolean object of class {@see SeedDMS_Core_DocumentContent}
2486     * or false
2487     */
2488    public function isLatestContent($version) { /* {{{ */
2489        return $this->getLatestContent()->getVersion() == $version;
2490    } /* }}} */
2491
2492    /**
2493     * @return bool|null|SeedDMS_Core_DocumentContent
2494     */
2495    private function __getLatestContent() { /* {{{ */
2496        if (!$this->_latestContent) {
2497            $db = $this->_dms->getDB();
2498            $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version` DESC LIMIT 1";
2499            $resArr = $db->getResultArray($queryStr);
2500            if (is_bool($resArr) && !$resArr)
2501                return false;
2502            if (count($resArr) != 1)
2503                return false;
2504
2505            $resArr = $resArr[0];
2506            $classname = $this->_dms->getClassname('documentcontent');
2507            $this->_latestContent = new $classname($resArr["id"], $this, $resArr["version"], $resArr["comment"], $resArr["date"], $resArr["createdBy"], $resArr["dir"], $resArr["orgFileName"], $resArr["fileType"], $resArr["mimeType"], $resArr['fileSize'], $resArr['checksum'], $resArr['revisiondate']);
2508        }
2509        return $this->_latestContent;
2510    } /* }}} */
2511
2512    /**
2513     * Get the latest version of document
2514     *
2515     * This method returns the latest accessible version of a document.
2516     * If content access has been restricted by setting
2517     * {@see SeedDMS_Core_DMS::noReadForStatus} the function will go
2518     * backwards in history until an accessible version is found. If none
2519     * is found null will be returned.
2520     * Access rights based on the document status are calculated for the
2521     * currently logged in user.
2522     *
2523     * @return bool|SeedDMS_Core_DocumentContent object of class {@see SeedDMS_Core_DocumentContent}
2524     */
2525    public function getLatestContent() { /* {{{ */
2526        if (!$this->_latestContent) {
2527            $db = $this->_dms->getDB();
2528            $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = ".$this->_id." ORDER BY `version` DESC";
2529            $resArr = $db->getResultArray($queryStr);
2530            if (is_bool($resArr) && !$resArr)
2531                return false;
2532
2533            $classname = $this->_dms->getClassname('documentcontent');
2534            $user = $this->_dms->getLoggedInUser();
2535            foreach ($resArr as $row) {
2536                /** @var SeedDMS_Core_DocumentContent $content */
2537                if (!$this->_latestContent) {
2538                    $content = new $classname($row["id"], $this, $row["version"], $row["comment"], $row["date"], $row["createdBy"], $row["dir"], $row["orgFileName"], $row["fileType"], $row["mimeType"], $row['fileSize'], $row['checksum'], $row['revisiondate']);
2539                    if($user) {
2540                        /* If the user may even write the document, then also allow to see all content.
2541                         * This is needed because the user could upload a new version
2542                         */
2543                        if($content->getAccessMode($user) >= M_READ) {
2544                            $this->_latestContent = $content;
2545                        }
2546                    } else {
2547                        $this->_latestContent = $content;
2548                    }
2549                }
2550            }
2551        }
2552
2553        return $this->_latestContent;
2554    } /* }}} */
2555
2556    /**
2557     * Remove version of document
2558     *
2559     * @param SeedDMS_Core_DocumentContent $version version number of content
2560     * @return boolean true if successful, otherwise false
2561     */
2562    private function _removeContent($version) { /* {{{ */
2563        $db = $this->_dms->getDB();
2564
2565        $db->startTransaction();
2566
2567        $status = $version->getStatus();
2568        $stID = $status["statusID"];
2569
2570        $queryStr = "DELETE FROM `tblDocumentContent` WHERE `document` = " . $this->getID() . " AND `version` = " . $version->getVersion();
2571        if (!$db->getResult($queryStr)) {
2572            $db->rollbackTransaction();
2573            return false;
2574        }
2575
2576        $queryStr = "DELETE FROM `tblDocumentContentAttributes` WHERE `content` = " . $version->getId();
2577        if (!$db->getResult($queryStr)) {
2578            $db->rollbackTransaction();
2579            return false;
2580        }
2581
2582        $queryStr = "DELETE FROM `tblTransmittalItems` WHERE `document` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2583        if (!$db->getResult($queryStr)) {
2584            $db->rollbackTransaction();
2585            return false;
2586        }
2587
2588        $queryStr = "DELETE FROM `tblDocumentStatusLog` WHERE `statusID` = '".$stID."'";
2589        if (!$db->getResult($queryStr)) {
2590            $db->rollbackTransaction();
2591            return false;
2592        }
2593
2594        $queryStr = "DELETE FROM `tblDocumentStatus` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2595        if (!$db->getResult($queryStr)) {
2596            $db->rollbackTransaction();
2597            return false;
2598        }
2599
2600        $status = $version->getReviewStatus();
2601        $stList = "";
2602        foreach ($status as $st) {
2603            $stList .= (strlen($stList)==0 ? "" : ", "). "'".$st["reviewID"]."'";
2604            $queryStr = "SELECT * FROM `tblDocumentReviewLog` WHERE `reviewID` = " . $st['reviewID'];
2605            $resArr = $db->getResultArray($queryStr);
2606            if ((is_bool($resArr) && !$resArr)) {
2607                $db->rollbackTransaction();
2608                return false;
2609            }
2610            foreach($resArr as $res) {
2611                if($storage = $this->_dms->getStorage()) {
2612                    $storage->deleteReview($document, $res['reviewLogID']);
2613                } else {
2614                $file = $this->_dms->contentDir . $this->getDir().'r'.$res['reviewLogID'];
2615                if(SeedDMS_Core_File::file_exists($file))
2616                    SeedDMS_Core_File::removeFile($file);
2617                }
2618            }
2619        }
2620
2621        if (strlen($stList)>0) {
2622            $queryStr = "DELETE FROM `tblDocumentReviewLog` WHERE `tblDocumentReviewLog`.`reviewID` IN (".$stList.")";
2623            if (!$db->getResult($queryStr)) {
2624                $db->rollbackTransaction();
2625                return false;
2626            }
2627        }
2628        $queryStr = "DELETE FROM `tblDocumentReviewers` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2629        if (!$db->getResult($queryStr)) {
2630            $db->rollbackTransaction();
2631            return false;
2632        }
2633        $status = $version->getApprovalStatus();
2634        $stList = "";
2635        foreach ($status as $st) {
2636            $stList .= (strlen($stList)==0 ? "" : ", "). "'".$st["approveID"]."'";
2637            $queryStr = "SELECT * FROM `tblDocumentApproveLog` WHERE `approveID` = " . $st['approveID'];
2638            $resArr = $db->getResultArray($queryStr);
2639            if ((is_bool($resArr) && !$resArr)) {
2640                $db->rollbackTransaction();
2641                return false;
2642            }
2643            foreach($resArr as $res) {
2644                if($storage = $this->_dms->getStorage()) {
2645                    $storage->deleteApproval($document, $res['approveLogID']);
2646                } else {
2647                $file = $this->_dms->contentDir . $this->getDir().'a'.$res['approveLogID'];
2648                if(SeedDMS_Core_File::file_exists($file))
2649                    SeedDMS_Core_File::removeFile($file);
2650                }
2651            }
2652        }
2653
2654        if (strlen($stList)>0) {
2655            $queryStr = "DELETE FROM `tblDocumentApproveLog` WHERE `tblDocumentApproveLog`.`approveID` IN (".$stList.")";
2656            if (!$db->getResult($queryStr)) {
2657                $db->rollbackTransaction();
2658                return false;
2659            }
2660        }
2661        $queryStr = "DELETE FROM `tblDocumentApprovers` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2662        if (!$db->getResult($queryStr)) {
2663            $db->rollbackTransaction();
2664            return false;
2665        }
2666
2667        /* Remove all receipts of document version.
2668         * This implmentation is different from the above for removing approvals
2669         * and reviews. It doesn't use getReceiptStatus() but reads the database
2670         */
2671        $queryStr = "SELECT * FROM `tblDocumentRecipients` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2672        $resArr = $db->getResultArray($queryStr);
2673        if ((is_bool($resArr) && !$resArr)) {
2674            $db->rollbackTransaction();
2675            return false;
2676        }
2677
2678        $stList = array();
2679        foreach($resArr as $res) {
2680            $stList[] = $res['receiptID'];
2681        }
2682
2683        if ($stList) {
2684            $queryStr = "DELETE FROM `tblDocumentReceiptLog` WHERE `receiptID` IN (".implode(',', $stList).")";
2685            if (!$db->getResult($queryStr)) {
2686                $db->rollbackTransaction();
2687                return false;
2688            }
2689            $queryStr = "DELETE FROM `tblDocumentRecipients` WHERE `receiptID` IN (".implode(',', $stList).")";
2690            if (!$db->getResult($queryStr)) {
2691                $db->rollbackTransaction();
2692                return false;
2693            }
2694        }
2695
2696        /* Remove all revisions of document version.
2697         * This implementation is different from the above for removing approvals
2698         * and reviews. It doesn't use getRevisionStatus() but reads the database
2699         */
2700        $queryStr = "SELECT * FROM `tblDocumentRevisors` WHERE `documentID` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2701        $resArr = $db->getResultArray($queryStr);
2702        if ((is_bool($resArr) && !$resArr)) {
2703            $db->rollbackTransaction();
2704            return false;
2705        }
2706
2707        $stList = array();
2708        foreach($resArr as $res) {
2709            $stList[] = $res['revisionID'];
2710        }
2711
2712        if ($stList) {
2713            $queryStr = "DELETE FROM `tblDocumentRevisionLog` WHERE `revisionID` IN (".implode(',', $stList).")";
2714            if (!$db->getResult($queryStr)) {
2715                $db->rollbackTransaction();
2716                return false;
2717            }
2718            $queryStr = "DELETE FROM `tblDocumentRevisors` WHERE `revisionID` IN (".implode(',', $stList).")";
2719            if (!$db->getResult($queryStr)) {
2720                $db->rollbackTransaction();
2721                return false;
2722            }
2723        }
2724
2725        $queryStr = "DELETE FROM `tblWorkflowDocumentContent` WHERE `document` = '". $this->getID() ."' AND `version` = '" . $version->getVersion()."'";
2726        if (!$db->getResult($queryStr)) {
2727            $db->rollbackTransaction();
2728            return false;
2729        }
2730
2731        /* Will be deleted automatically when record will be deleted
2732         * from tblWorkflowDocumentContent
2733        $queryStr = "DELETE FROM `tblWorkflowLog` WHERE `document` = '". $this->getID() ."' AND `version` = '" . $version->getVersion."'";
2734        if (!$db->getResult($queryStr)) {
2735            $db->rollbackTransaction();
2736            return false;
2737        }
2738         */
2739
2740        // remove only those document files attached to version
2741        $res = $this->getDocumentFiles($version->getVersion(), false);
2742        if (is_bool($res) && !$res) {
2743            $db->rollbackTransaction();
2744            return false;
2745        }
2746
2747        foreach ($res as $documentfile)
2748            if(!$this->removeDocumentFile($documentfile->getId())) {
2749                $db->rollbackTransaction();
2750                return false;
2751            }
2752
2753        if($storage = $this->_dms->getStorage()) {
2754            if(!$storage->deleteContent($this, $version)) {
2755                $db->rollbackTransaction();
2756                return false;
2757            }
2758        } else {
2759        if (SeedDMS_Core_File::file_exists( $this->_dms->contentDir.$version->getPath() ))
2760            if (!SeedDMS_Core_File::removeFile( $this->_dms->contentDir.$version->getPath() )) {
2761                $db->rollbackTransaction();
2762                return false;
2763            }
2764        }
2765
2766        $db->commitTransaction();
2767        return true;
2768    } /* }}} */
2769
2770    /**
2771     * Call callback onPreRemoveDocument before deleting content
2772     *
2773     * @param SeedDMS_Core_DocumentContent $version version number of content
2774     * @return bool|mixed
2775     */
2776    public function removeContent($version) { /* {{{ */
2777        $this->_dms->lasterror = '';
2778        $db = $this->_dms->getDB();
2779
2780        /* Make sure the version exists */
2781        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = " . $this->getID() . " AND `version` = " . $version->getVersion();
2782        $resArr = $db->getResultArray($queryStr);
2783        if (is_bool($resArr) && !$resArr)
2784            return false;
2785        if (count($resArr)==0)
2786            return false;
2787
2788        /* Make sure this is not the last version */
2789        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `document` = " . $this->getID();
2790        $resArr = $db->getResultArray($queryStr);
2791        if (is_bool($resArr) && !$resArr)
2792            return false;
2793        if (count($resArr)==1)
2794            return false;
2795
2796        /* Check if 'onPreRemoveDocument' callback is set */
2797        if(isset($this->_dms->callbacks['onPreRemoveContent'])) {
2798            foreach($this->_dms->callbacks['onPreRemoveContent'] as $callback) {
2799                $ret = call_user_func($callback[0], $callback[1], $this, $version);
2800                if(is_bool($ret))
2801                    return $ret;
2802            }
2803        }
2804
2805        if(false === ($ret = self::_removeContent($version))) {
2806            return false;
2807        }
2808
2809        /* Invalidate the content list and the latest content of this document,
2810         * otherwise getContent() and getLatestContent()
2811         * will still return the content just deleted.
2812         */
2813        $this->_latestContent = null;
2814        $this->_content = null;
2815
2816        /* Check if 'onPostRemoveDocument' callback is set */
2817        if(isset($this->_dms->callbacks['onPostRemoveContent'])) {
2818            foreach($this->_dms->callbacks['onPostRemoveContent'] as $callback) {
2819                if(!call_user_func($callback[0], $callback[1], $version)) {
2820                }
2821            }
2822        }
2823
2824        return $ret;
2825    } /* }}} */
2826
2827    /**
2828     * Return a certain document link
2829     *
2830     * @param integer $linkID id of link
2831     * @return SeedDMS_Core_DocumentLink|bool of SeedDMS_Core_DocumentLink or false in case of
2832     *         an error.
2833     */
2834    public function getDocumentLink($linkID) { /* {{{ */
2835        $db = $this->_dms->getDB();
2836
2837        if (!is_numeric($linkID)) return false;
2838
2839        $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $linkID;
2840        $resArr = $db->getResultArray($queryStr);
2841        if (is_bool($resArr) && !$resArr)
2842            return false;
2843        if (count($resArr)==0)
2844            return null;
2845
2846        $resArr = $resArr[0];
2847        $document = $this->_dms->getDocument($resArr["document"]);
2848        $target = $this->_dms->getDocument($resArr["target"]);
2849        if($document && $target) {
2850            $link = new SeedDMS_Core_DocumentLink($resArr["id"], $document, $target, $resArr["userID"], $resArr["public"]);
2851            $user = $this->_dms->getLoggedInUser();
2852            if($link->getAccessMode($user, $document, $target) >= M_READ)
2853                return $link;
2854        }
2855        return null;
2856    } /* }}} */
2857
2858    /**
2859     * Return all document links
2860     *
2861     * The list may contain all links to other documents, even those which
2862     * may not be visible by certain users, unless you pass appropriate
2863     * parameters to filter out public links and those created by
2864     * the given user. The two parameters are or'ed. If $publiconly
2865     * is set the method will return all public links disregarding the
2866     * user. If $publiconly is not set but a user is set, the method
2867     * will return all links of that user (public and none public).
2868     * Setting a user and $publiconly to true will *not* return the
2869     * public links of that user but all links which are public or
2870     * owned by that user.
2871     *
2872     * The application must call
2873     * SeedDMS_Core_DMS::filterDocumentLinks() afterwards to filter out
2874     * those links pointing to a document not accessible by a given user.
2875     *
2876     * @param boolean           $publiconly return all publically visible links
2877     * @param SeedDMS_Core_User $user       return also private links of this user
2878     *
2879     * @return array list of objects of class {@see SeedDMS_Core_DocumentLink}
2880     */
2881    public function getDocumentLinks($publiconly=false, $user=null) { /* {{{ */
2882        if (!isset($this->_documentLinks)) {
2883            $db = $this->_dms->getDB();
2884
2885            $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `document` = " . $this->_id;
2886            $tmp = array();
2887            if($publiconly)
2888                $tmp[] = "`public`=1";
2889            if($user)
2890                $tmp[] = "`userID`=".$user->getID();
2891            if($tmp) {
2892                $queryStr .= " AND (".implode(" OR ", $tmp).")";
2893            }
2894
2895            $resArr = $db->getResultArray($queryStr);
2896            if (is_bool($resArr) && !$resArr)
2897                return false;
2898            $this->_documentLinks = array();
2899
2900            $user = $this->_dms->getLoggedInUser();
2901            foreach ($resArr as $row) {
2902                $target = $this->_dms->getDocument($row["target"]);
2903                if($target) {
2904                    $link = new SeedDMS_Core_DocumentLink($row["id"], $this, $target, $row["userID"], $row["public"]);
2905                    if($link->getAccessMode($user, $this, $target) >= M_READ)
2906                        array_push($this->_documentLinks, $link);
2907                }
2908            }
2909        }
2910        return $this->_documentLinks;
2911    } /* }}} */
2912
2913    /**
2914     * Return all document having a link on this document
2915     *
2916     * The list contains all documents which have a link to the current
2917     * document. The list contains even those documents which
2918     * may not be accessible by the user, unless you pass appropriate
2919     * parameters to filter out public links and those created by
2920     * the given user.
2921     * This method is basically the reverse of
2922     * {@see SeedDMS_Core_Document::getDocumentLinks()}
2923     *
2924     * The application must call
2925     * SeedDMS_Core_DMS::filterDocumentLinks() afterwards to filter out
2926     * those links pointing to a document not accessible by a given user.
2927     *
2928     * @param boolean           $publiconly return all publically visible links
2929     * @param SeedDMS_Core_User $user       return also private links of this user
2930     *
2931     * @return array list of objects of class SeedDMS_Core_DocumentLink
2932     */
2933    public function getReverseDocumentLinks($publiconly=false, $user=null) { /* {{{ */
2934        $db = $this->_dms->getDB();
2935
2936        $queryStr = "SELECT * FROM `tblDocumentLinks` WHERE `target` = " . $this->_id;
2937        $tmp = array();
2938        if($publiconly)
2939            $tmp[] = "`public`=1";
2940        if($user)
2941            $tmp[] = "`userID`=".$user->getID();
2942        if($tmp) {
2943            $queryStr .= " AND (".implode(" OR ", $tmp).")";
2944        }
2945
2946        $resArr = $db->getResultArray($queryStr);
2947        if (is_bool($resArr) && !$resArr)
2948            return false;
2949
2950        $links = array();
2951        foreach ($resArr as $row) {
2952            $document = $this->_dms->getDocument($row["document"]);
2953            $link = new SeedDMS_Core_DocumentLink($row["id"], $document, $this, $row["userID"], $row["public"]);
2954            if($link->getAccessMode($user, $document, $this) >= M_READ)
2955                array_push($links, $link);
2956        }
2957
2958        return $links;
2959    } /* }}} */
2960
2961    /**
2962     * Add a link to a target document
2963     *
2964     * @param int $targetID Id of target document
2965     * @param int $userID Id of user adding the link
2966     * @param boolean true if link is public
2967     * @return SeedDMS_Core_DocumentLink|boolean
2968     */
2969    public function addDocumentLink($targetID, $userID, $public) { /* {{{ */
2970        $db = $this->_dms->getDB();
2971
2972        $public = ($public) ? 1 : 0;
2973
2974        if (!is_numeric($targetID) || $targetID < 1)
2975            return false;
2976
2977        if ($targetID == $this->_id)
2978            return false;
2979
2980        if (!is_numeric($userID) || $userID < 1)
2981            return false;
2982
2983        if(!($target = $this->_dms->getDocument($targetID)))
2984            return false;
2985
2986        if(!($user = $this->_dms->getUser($userID)))
2987            return false;
2988
2989        $queryStr = "INSERT INTO `tblDocumentLinks` (`document`, `target`, `userID`, `public`) VALUES (".$this->_id.", ".(int)$targetID.", ".(int)$userID.", ".$public.")";
2990        if (!$db->getResult($queryStr))
2991            return false;
2992
2993        unset($this->_documentLinks);
2994
2995        $id = $db->getInsertID('tblDocumentLinks');
2996        $link = new SeedDMS_Core_DocumentLink($id, $this, $target, $user->getId(), $public);
2997        return $link;
2998    } /* }}} */
2999
3000    public function removeDocumentLink($linkID) { /* {{{ */
3001        $db = $this->_dms->getDB();
3002
3003        if (!is_numeric($linkID) || $linkID < 1)
3004            return false;
3005
3006        $queryStr = "DELETE FROM `tblDocumentLinks` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $linkID;
3007        if (!$db->getResult($queryStr)) return false;
3008        unset ($this->_documentLinks);
3009        return true;
3010    } /* }}} */
3011
3012    /**
3013     * Get attached file by its id
3014     *
3015     * @return object instance of SeedDMS_Core_DocumentFile, null if file is not
3016     * accessible, false in case of an sql error
3017     */
3018    public function getDocumentFile($ID) { /* {{{ */
3019        $db = $this->_dms->getDB();
3020
3021        if (!is_numeric($ID)) return false;
3022
3023        $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id ." AND `id` = " . (int) $ID;
3024        $resArr = $db->getResultArray($queryStr);
3025        if ((is_bool($resArr) && !$resArr) || count($resArr)==0) return false;
3026
3027        $resArr = $resArr[0];
3028        $classname = $this->_dms->getClassname('documentfile');
3029        $file = new $classname($resArr["id"], $this, $resArr["userID"], $resArr["comment"], $resArr["date"], $resArr["dir"], $resArr["fileType"], $resArr["mimeType"], $resArr["orgFileName"], $resArr["name"],$resArr["version"],$resArr["public"]);
3030        $user = $this->_dms->getLoggedInUser();
3031        if($file->getAccessMode($user) >= M_READ)
3032            return $file;
3033        return null;
3034    } /* }}} */
3035
3036    /**
3037     * Get list of files attached to document
3038     *
3039     * @param integer $version      get only attachments for this version
3040     * @param boolean $incnoversion include attachments without a version
3041     *
3042     * @return array list of files, false in case of an sql error
3043     */
3044    public function getDocumentFiles($version=0, $incnoversion=true) { /* {{{ */
3045        /* use a smarter caching because removing a document will call this function
3046         * for each version and the document itself.
3047         */
3048        $hash = substr(md5($version.$incnoversion), 0, 4);
3049        if (!isset($this->_documentFiles[$hash])) {
3050            $db = $this->_dms->getDB();
3051
3052            $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id;
3053            if($version) {
3054                if($incnoversion)
3055                    $queryStr .= " AND (`version`=0 OR `version`=".(int) $version.")";
3056                else
3057                    $queryStr .= " AND (`version`=".(int) $version.")";
3058            }
3059            $queryStr .= " ORDER BY ";
3060            if($version) {
3061                $queryStr .= "`version` DESC,";
3062            }
3063            $queryStr .= "`date` DESC";
3064            $resArr = $db->getResultArray($queryStr);
3065            if (is_bool($resArr) && !$resArr) return false;
3066
3067            $this->_documentFiles = array($hash=>array());
3068
3069            $user = $this->_dms->getLoggedInUser();
3070            $classname = $this->_dms->getClassname('documentfile');
3071            foreach ($resArr as $row) {
3072                $file = new $classname($row["id"], $this, $row["userID"], $row["comment"], $row["date"], $row["dir"], $row["fileType"], $row["mimeType"], $row["orgFileName"], $row["name"], $row["version"], $row["public"]);
3073                if($file->getAccessMode($user) >= M_READ)
3074                    array_push($this->_documentFiles[$hash], $file);
3075            }
3076        }
3077        return $this->_documentFiles[$hash];
3078    } /* }}} */
3079
3080    /**
3081     * Add an attachment to the document
3082     *
3083     */
3084    public function addDocumentFile($name, $comment, $user, $tmpFile, $orgFileName, $fileType, $mimeType, $version=0, $public=1) { /* {{{ */
3085        $db = $this->_dms->getDB();
3086
3087        $dir = $this->getDir();
3088
3089        $db->startTransaction();
3090        $queryStr = "INSERT INTO `tblDocumentFiles` (`comment`, `date`, `dir`, `document`, `fileType`, `mimeType`, `orgFileName`, `userID`, `name`, `version`, `public`) VALUES ".
3091            "(".$db->qstr($comment).", ".$db->getCurrentTimestamp().", ".$db->qstr($dir).", ".$this->_id.", ".$db->qstr($fileType).", ".$db->qstr($mimeType).", ".$db->qstr($orgFileName).",".$user->getID().",".$db->qstr($name).", ".((int) $version).", ".($public ? 1 : 0).")";
3092        if (!$db->getResult($queryStr)) {
3093            $db->rollbackTransaction();
3094            return false;
3095        }
3096
3097        $id = $db->getInsertID('tblDocumentFiles');
3098
3099        $file = $this->getDocumentFile($id);
3100        if (is_bool($file) && !$file) {
3101            $db->rollbackTransaction();
3102            return false;
3103        }
3104
3105        if($storage = $this->_dms->getStorage()) {
3106            $err = $storage->saveAttachment($this, $file, $tmpFile);
3107        } else {
3108        // copy file
3109        if (!SeedDMS_Core_File::makeDir($this->_dms->contentDir . $dir)) return false;
3110        if($this->_dms->forceRename)
3111            $err = SeedDMS_Core_File::renameFile($tmpFile, $this->_dms->contentDir . $file->getPath());
3112        else
3113            $err = SeedDMS_Core_File::copyFile($tmpFile, $this->_dms->contentDir . $file->getPath());
3114        }
3115        if (!$err) {
3116            $db->rollbackTransaction();
3117            return false;
3118        }
3119
3120        $db->commitTransaction();
3121        unset ($this->_documentFiles);
3122        return $file;
3123    } /* }}} */
3124
3125    public function removeDocumentFile($ID) { /* {{{ */
3126        $db = $this->_dms->getDB();
3127
3128        if (!is_numeric($ID) || $ID < 1)
3129            return false;
3130
3131        $file = $this->getDocumentFile($ID);
3132        if (is_bool($file) && !$file) return false;
3133
3134        $db->startTransaction();
3135        /* First delete the database record, because that can be undone
3136         * if deletion of the file fails.
3137         */
3138        $queryStr = "DELETE FROM `tblDocumentFiles` WHERE `document` = " . $this->getID() . " AND `id` = " . (int) $ID;
3139        if (!$db->getResult($queryStr)) {
3140            $db->rollbackTransaction();
3141            return false;
3142        }
3143
3144        if($storage = $this->_dms->getStorage()) {
3145            if(!$storage->deleteAttachment($this, $file)) {
3146                $db->rollbackTransaction();
3147                return false;
3148            }
3149        } else {
3150        if (SeedDMS_Core_File::file_exists( $this->_dms->contentDir . $file->getPath() )){
3151            if (!SeedDMS_Core_File::removeFile( $this->_dms->contentDir . $file->getPath() )) {
3152                $db->rollbackTransaction();
3153                return false;
3154            }
3155        }
3156        }
3157
3158        $db->commitTransaction();
3159        unset ($this->_documentFiles);
3160
3161        return true;
3162    } /* }}} */
3163
3164    /**
3165     * Remove a document completly
3166     *
3167     * This methods calls the callback 'onPreRemoveDocument' before removing
3168     * the document. The current document will be passed as the second
3169     * parameter to the callback function. After successful deletion the
3170     * 'onPostRemoveDocument' callback will be used. The current document id
3171     * will be passed as the second parameter. If onPreRemoveDocument fails
3172     * the whole function will fail and the document will not be deleted.
3173     * The return value of 'onPostRemoveDocument' will be disregarded.
3174     *
3175     * @return boolean true on success, otherwise false
3176     */
3177    public function remove() { /* {{{ */
3178        $db = $this->_dms->getDB();
3179        $this->_dms->lasterror = '';
3180
3181        /* Check if 'onPreRemoveDocument' callback is set */
3182        if(isset($this->_dms->callbacks['onPreRemoveDocument'])) {
3183            foreach($this->_dms->callbacks['onPreRemoveDocument'] as $callback) {
3184                $ret = call_user_func($callback[0], $callback[1], $this);
3185                if(is_bool($ret))
3186                    return $ret;
3187            }
3188        }
3189
3190        $res = $this->getContent();
3191        if (is_bool($res) && !$res) return false;
3192
3193        $db->startTransaction();
3194
3195        // remove content of document
3196        foreach ($this->_content as $version) {
3197            if (!$this->_removeContent($version)) {
3198                $db->rollbackTransaction();
3199                return false;
3200            }
3201        }
3202
3203        // remove all document files
3204        $res = $this->getDocumentFiles();
3205        if (is_bool($res) && !$res) {
3206            $db->rollbackTransaction();
3207            return false;
3208        }
3209
3210        foreach ($res as $documentfile)
3211            if(!$this->removeDocumentFile($documentfile->getId())) {
3212                $db->rollbackTransaction();
3213                return false;
3214            }
3215
3216        // TODO: versioning file?
3217
3218        if($storage = $this->_dms->getStorage()) {
3219            if(!$storage->deleteDocDir($this)) {
3220                $db->rollbackTransaction();
3221                return false;
3222            }
3223        } else {
3224        if (SeedDMS_Core_File::file_exists( $this->_dms->contentDir . $this->getDir() ))
3225            if (!SeedDMS_Core_File::removeDir( $this->_dms->contentDir . $this->getDir() )) {
3226                $db->rollbackTransaction();
3227                return false;
3228            }
3229        }
3230
3231        $queryStr = "DELETE FROM `tblDocuments` WHERE `id` = " . $this->_id;
3232        if (!$db->getResult($queryStr)) {
3233            $db->rollbackTransaction();
3234            return false;
3235        }
3236        $queryStr = "DELETE FROM `tblDocumentAttributes` WHERE `document` = " . $this->_id;
3237        if (!$db->getResult($queryStr)) {
3238            $db->rollbackTransaction();
3239            return false;
3240        }
3241        $queryStr = "DELETE FROM `tblACLs` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT;
3242        if (!$db->getResult($queryStr)) {
3243            $db->rollbackTransaction();
3244            return false;
3245        }
3246        $queryStr = "DELETE FROM `tblDocumentLinks` WHERE `document` = " . $this->_id . " OR `target` = " . $this->_id;
3247        if (!$db->getResult($queryStr)) {
3248            $db->rollbackTransaction();
3249            return false;
3250        }
3251        $queryStr = "DELETE FROM `tblDocumentLocks` WHERE `document` = " . $this->_id;
3252        if (!$db->getResult($queryStr)) {
3253            $db->rollbackTransaction();
3254            return false;
3255        }
3256        $queryStr = "DELETE FROM `tblDocumentCheckOuts` WHERE `document` = " . $this->_id;
3257        if (!$db->getResult($queryStr)) {
3258            $db->rollbackTransaction();
3259            return false;
3260        }
3261        $queryStr = "DELETE FROM `tblDocumentFiles` WHERE `document` = " . $this->_id;
3262        if (!$db->getResult($queryStr)) {
3263            $db->rollbackTransaction();
3264            return false;
3265        }
3266        $queryStr = "DELETE FROM `tblDocumentCategory` WHERE `documentID` = " . $this->_id;
3267        if (!$db->getResult($queryStr)) {
3268            $db->rollbackTransaction();
3269            return false;
3270        }
3271
3272        // Delete the notification list.
3273        $queryStr = "DELETE FROM `tblNotify` WHERE `target` = " . $this->_id . " AND `targetType` = " . T_DOCUMENT;
3274        if (!$db->getResult($queryStr)) {
3275            $db->rollbackTransaction();
3276            return false;
3277        }
3278
3279        $db->commitTransaction();
3280
3281        /* Check if 'onPostRemoveDocument' callback is set */
3282        if(isset($this->_dms->callbacks['onPostRemoveDocument'])) {
3283            foreach($this->_dms->callbacks['onPostRemoveDocument'] as $callback) {
3284                if(!call_user_func($callback[0], $callback[1], $this)) {
3285                }
3286            }
3287        }
3288
3289        return true;
3290    } /* }}} */
3291
3292    /**
3293     * Get List of users and groups which have read access on the document.
3294     * The list will not include any guest users,
3295     * administrators and the owner of the document.
3296     *
3297     * This method is deprecated. Use
3298     * {@see SeedDMS_Core_Document::getReadAccessList()} instead.
3299     */
3300    protected function __getApproversList() { /* {{{ */
3301        return $this->getReadAccessList(0, 0, 0);
3302    } /* }}} */
3303
3304    /**
3305     * Returns a list of groups and users with read access on the document
3306     *
3307     * @param boolean $listadmin if set to true any admin will be listed too
3308     * @param boolean $listowner if set to true the owner will be listed too
3309     * @param boolean $listguest if set to true any guest will be listed too
3310     *
3311     * @return array list of users and groups
3312     */
3313    public function getReadAccessList($listadmin=0, $listowner=0, $listguest=0) { /* {{{ */
3314        $db = $this->_dms->getDB();
3315
3316        $cachehash = substr(md5($listadmin.$listowner.$listguest), 0, 3);
3317        if (!isset($this->_readAccessList[$cachehash])) {
3318            $this->_readAccessList[$cachehash] = array("groups" => array(), "users" => array());
3319            $userIDs = "";
3320            $groupIDs = "";
3321            $defAccess  = $this->getDefaultAccess();
3322
3323            /* Check if the default access is < read access or >= read access.
3324             * If default access is less than read access, then create a list
3325             * of users and groups with read access.
3326             * If default access is equal or greater then read access, then
3327             * create a list of users and groups without read access.
3328             */
3329            if ($defAccess<M_READ) {
3330                // Get the list of all users and groups that are listed in the ACL as
3331                // having read access to the document.
3332                $tmpList = $this->getAccessList(M_READ, O_GTEQ);
3333            }
3334            else {
3335                // Get the list of all users and groups that DO NOT have read access
3336                // to the document.
3337                $tmpList = $this->getAccessList(M_NONE, O_LTEQ);
3338            }
3339            /** @var SeedDMS_Core_GroupAccess $groupAccess */
3340            foreach ($tmpList["groups"] as $groupAccess) {
3341                $groupIDs .= (strlen($groupIDs)==0 ? "" : ", ") . $groupAccess->getGroupID();
3342            }
3343
3344            /** @var SeedDMS_Core_UserAccess $userAccess */
3345            foreach ($tmpList["users"] as $userAccess) {
3346                $user = $userAccess->getUser();
3347//                if (!$listadmin && $user->isAdmin()) continue;
3348//                if (!$listowner && $user->getID() == $this->_ownerID) continue;
3349//                if (!$listguest && $user->isGuest()) continue;
3350                $userIDs .= (strlen($userIDs)==0 ? "" : ", ") . $user->getID();
3351            }
3352
3353            // Construct a query against the users table to identify those users
3354            // that have read access on this document, either directly through an
3355            // ACL entry, by virtue of ownership or by having administrative rights
3356            // on the database.
3357            $queryStr="";
3358            /* If default access is less then read, $userIDs and $groupIDs contains
3359             * a list of user with read access
3360             */
3361            if ($defAccess < M_READ) {
3362                $queryStr = "SELECT DISTINCT `tblUsers`.* FROM `tblUsers` ".
3363                    "LEFT JOIN `tblGroupMembers` ON `tblGroupMembers`.`userID`=`tblUsers`.`id` ".
3364                    "LEFT JOIN `tblRoles` ON `tblRoles`.`id`=`tblUsers`.`role` ".
3365                    "WHERE 1=0".
3366                    ((strlen($groupIDs) > 0) ? " OR (`tblGroupMembers`.`groupID` IN (". $groupIDs ."))" : "").
3367                    ((strlen($userIDs) > 0) ?  " OR (`tblUsers`.`id` IN (". $userIDs ."))" : "").
3368                    " OR (`tblRoles`.`role` = ".SeedDMS_Core_Role::role_admin.")".
3369                    " OR (`tblUsers`.`id` = ". $this->_ownerID . ")".
3370                    " ORDER BY `login`";
3371            }
3372            /* If default access is equal or greater than M_READ, $userIDs and
3373             * $groupIDs contains a list of user without read access
3374             * The sql statement will exclude those users and groups but include
3375             * admins and the owner
3376             */
3377            else {
3378                $queryStr = "SELECT DISTINCT `tblUsers`.* FROM `tblUsers` ".
3379                    "LEFT JOIN `tblGroupMembers` ON `tblGroupMembers`.`userID`=`tblUsers`.`id` ".
3380                    "LEFT JOIN `tblRoles` ON `tblRoles`.`id`=`tblUsers`.`role` ".
3381                    "WHERE 1=1".
3382                    (strlen($groupIDs) == 0 ? "" : " AND (`tblGroupMembers`.`groupID` NOT IN (". $groupIDs .") OR `tblGroupMembers`.`groupID` IS NULL)").
3383                    (strlen($userIDs) == 0 ? "" : " AND (`tblUsers`.`id` NOT IN (". $userIDs ."))").
3384                    " OR `tblUsers`.`id` = ". $this->_ownerID . " OR `tblRoles`.`role` = ".SeedDMS_Core_Role::role_admin." ORDER BY `login` ";
3385            }
3386            $resArr = $db->getResultArray($queryStr);
3387            if (!is_bool($resArr)) {
3388                foreach ($resArr as $row) {
3389                    $user = $this->_dms->getUser($row['id']);
3390                    if (!$listadmin && $user->isAdmin()) continue;
3391                    if (!$listowner && $user->getID() == $this->_ownerID) continue;
3392                    if (!$listguest && $user->isGuest()) continue;
3393                    $this->_readAccessList[$cachehash]["users"][] = $user;
3394                }
3395            }
3396
3397            // Assemble the list of groups that have read access to the document.
3398            $queryStr="";
3399            if ($defAccess < M_READ) {
3400                if (strlen($groupIDs)>0) {
3401                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ".
3402                        "WHERE `tblGroups`.`id` IN (". $groupIDs .") ORDER BY `name`";
3403                }
3404            }
3405            else {
3406                if (strlen($groupIDs)>0) {
3407                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ".
3408                        "WHERE `tblGroups`.`id` NOT IN (". $groupIDs .") ORDER BY `name`";
3409                }
3410                else {
3411                    $queryStr = "SELECT `tblGroups`.* FROM `tblGroups` ORDER BY `name`";
3412                }
3413            }
3414            if (strlen($queryStr)>0) {
3415                $resArr = $db->getResultArray($queryStr);
3416                if (!is_bool($resArr)) {
3417                    foreach ($resArr as $row) {
3418                        $group = $this->_dms->getGroup($row["id"]);
3419                        $this->_readAccessList[$cachehash]["groups"][] = $group;
3420                    }
3421                }
3422            }
3423        }
3424        return $this->_readAccessList[$cachehash];
3425    } /* }}} */
3426
3427    /**
3428     * Get the internally used folderList which stores the ids of folders from
3429     * the root folder to the parent folder.
3430     *
3431     * @return string column separated list of folder ids
3432     */
3433    public function getFolderList() { /* {{{ */
3434        $db = $this->_dms->getDB();
3435
3436        $queryStr = "SELECT `folderList` FROM `tblDocuments` WHERE id = ".$this->_id;
3437        $resArr = $db->getResultArray($queryStr);
3438        if (is_bool($resArr) && !$resArr)
3439            return false;
3440
3441        return $resArr[0]['folderList'];
3442    } /* }}} */
3443
3444    /**
3445     * Checks the internal data of the document and repairs it.
3446     * Currently, this function only repairs an incorrect folderList
3447     *
3448     * @return boolean true on success, otherwise false
3449     */
3450    public function repair() { /* {{{ */
3451        $db = $this->_dms->getDB();
3452
3453        $curfolderlist = $this->getFolderList();
3454
3455        // calculate the folderList of the folder
3456        $parent = $this->getFolder();
3457        $pathPrefix="";
3458        $path = $parent->getPath();
3459        foreach ($path as $f) {
3460            $pathPrefix .= ":".$f->getID();
3461        }
3462        if (strlen($pathPrefix)>1) {
3463            $pathPrefix .= ":";
3464        }
3465        if($curfolderlist != $pathPrefix) {
3466            $queryStr = "UPDATE `tblDocuments` SET `folderList`='".$pathPrefix."' WHERE `id` = ". $this->_id;
3467            $res = $db->getResult($queryStr);
3468            if (!$res)
3469                return false;
3470        }
3471        return true;
3472    } /* }}} */
3473
3474    /**
3475     * Calculate the disk space including all versions of the document
3476     *
3477     * This is done by using the internal database field storing the
3478     * filesize of a document version.
3479     *
3480     * @return integer total disk space in Bytes
3481     */
3482    public function getUsedDiskSpace(): int { /* {{{ */
3483        $db = $this->_dms->getDB();
3484
3485        $queryStr = "SELECT SUM(`fileSize`) sum FROM `tblDocumentContent` WHERE `document` = " . $this->_id;
3486        $resArr = $db->getResultArray($queryStr);
3487        if (is_bool($resArr) && $resArr == false)
3488            return false;
3489
3490        return (int) $resArr[0]['sum'];
3491    } /* }}} */
3492
3493    /**
3494     * Returns a list of events happend during the life of the document
3495     *
3496     * This includes the creation of new versions, approval and reviews, etc.
3497     *
3498     * @return array list of events
3499     */
3500    public function getTimeline() { /* {{{ */
3501        $db = $this->_dms->getDB();
3502
3503        $timeline = array();
3504
3505        $lc=$this->getLatestContent();
3506        $queryStr = "SELECT `revisiondate`, `version` FROM `tblDocumentContent` WHERE `document` = " . $this->_id . " AND `version` = " . $lc->getVersion();
3507        $resArr = $db->getResultArray($queryStr);
3508        if (is_bool($resArr) && $resArr == false)
3509            return false;
3510
3511        foreach ($resArr as $row) {
3512            if($row['revisiondate'] && substr($row['revisiondate'], 0, 4) != '0000')
3513                $timeline[] = array('date'=>substr($row['revisiondate'], 0, 10)." 00:00:00", 'allday'=>true, 'msg'=>'Scheduled revision of version '.$row['version'], 'type'=>'scheduled_revision', 'version'=>$row['version'], 'document'=>$this, 'params'=>array($row['version']));
3514        }
3515
3516        $queryStr = "SELECT * FROM `tblDocumentFiles` WHERE `document` = " . $this->_id;
3517        $resArr = $db->getResultArray($queryStr);
3518        if (is_bool($resArr) && $resArr == false)
3519            return false;
3520
3521        foreach ($resArr as $row) {
3522            $date = date('Y-m-d H:i:s', (int) $row['date']);
3523            $timeline[] = array('date'=>$date, 'msg'=>'Added attachment "'.$row['name'].'"', 'document'=>$this, 'type'=>'add_file', 'fileid'=>$row['id']);
3524        }
3525
3526        $queryStr=
3527            "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`statusLogID`,`tblDocumentStatusLog`.`status`, ".
3528            "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ".
3529            "`tblDocumentStatusLog`.`userID` ".
3530            "FROM `tblDocumentStatus` ".
3531            "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ".
3532            "WHERE `tblDocumentStatus`.`documentID` = '". $this->_id ."' ".
3533            "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC";
3534        $resArr = $db->getResultArray($queryStr);
3535        if (is_bool($resArr) && !$resArr)
3536            return false;
3537
3538        /* The above query will also contain entries where a document status exists
3539         * but no status log entry. Those records will have no date and must be
3540         * skipped.
3541         */
3542        foreach ($resArr as $row) {
3543            if($row['date']) {
3544                $date = $row['date'];
3545                $timeline[] = array('date'=>$date, 'msg'=>'Version '.$row['version'].': Status change to '.$row['status'], 'type'=>'status_change', 'version'=>$row['version'], 'document'=>$this, 'status'=>$row['status'], 'statusid'=>$row['statusID'], 'statuslogid'=>$row['statusLogID']);
3546            }
3547        }
3548        return $timeline;
3549    } /* }}} */
3550
3551    /**
3552     * Transfers the document to a new user
3553     * 
3554     * This method not just sets a new owner of the document but also
3555     * transfers the document links, attachments and locks to the new user.
3556     *
3557     * @return boolean true if successful, otherwise false
3558     */
3559    public function transferToUser($newuser) { /* {{{ */
3560        $db = $this->_dms->getDB();
3561
3562        if($newuser->getId() == $this->_ownerID)
3563            return true;
3564
3565        $db->startTransaction();
3566        $queryStr = "UPDATE `tblDocuments` SET `owner` = ".$newuser->getId()." WHERE `id` = " . $this->_id;
3567        if (!$db->getResult($queryStr)) {
3568            $db->rollbackTransaction();
3569            return false;
3570        }
3571
3572        $queryStr = "UPDATE `tblDocumentLocks` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID;
3573        if (!$db->getResult($queryStr)) {
3574            $db->rollbackTransaction();
3575            return false;
3576        }
3577
3578        $queryStr = "UPDATE `tblDocumentLinks` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID;
3579        if (!$db->getResult($queryStr)) {
3580            $db->rollbackTransaction();
3581            return false;
3582        }
3583
3584        $queryStr = "UPDATE `tblDocumentFiles` SET `userID` = ".$newuser->getId()." WHERE `document` = " . $this->_id . " AND `userID` = ".$this->_ownerID;
3585        if (!$db->getResult($queryStr)) {
3586            $db->rollbackTransaction();
3587            return false;
3588        }
3589
3590        $this->_ownerID = $newuser->getID();
3591        $this->_owner = $newuser;
3592
3593        $db->commitTransaction();
3594        return true;
3595    } /* }}} */
3596
3597} /* }}} */
3598
3599
3600/**
3601 * Class to represent content of a document
3602 *
3603 * Each document has content attached to it, often called a 'version' of the
3604 * document. The document content represents a file on the disk with some
3605 * meta data stored in the database. A document content has a version number
3606 * which is incremented with each replacement of the old content. Old versions
3607 * are kept unless they are explicitly deleted by
3608 * {@see SeedDMS_Core_Document::removeContent()}.
3609 *
3610 * @category   DMS
3611 * @package    SeedDMS_Core
3612 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
3613 *             Uwe Steinmann <uwe@steinmann.cx>
3614 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
3615 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
3616 *             2010-2024 Uwe Steinmann
3617 * @version    Release: @package_version@
3618 */
3619class SeedDMS_Core_DocumentContent extends SeedDMS_Core_Object { /* {{{ */
3620    /**
3621     * @var object document
3622     */
3623    protected $_document;
3624
3625    /**
3626     * @var integer version
3627     */
3628    protected $_version;
3629
3630    /**
3631     * @var string comment
3632     */
3633    protected $_comment;
3634
3635    /**
3636     * @var string date
3637     */
3638    protected $_date;
3639
3640    /**
3641     * @var integer $_userID
3642     */
3643    protected $_userID;
3644
3645    /**
3646     * @var object $_user
3647     */
3648    protected $_user;
3649
3650    /**
3651     * @var string dir on disk (deprecated)
3652     */
3653    protected $_dir;
3654
3655    /**
3656     * @var string original file name
3657     */
3658    protected $_orgFileName;
3659
3660    /**
3661     * @var string file type (actually the extension without the leading dot)
3662     */
3663    protected $_fileType;
3664
3665    /**
3666     * @var string mime type
3667     */
3668    protected $_mimeType;
3669
3670    /**
3671     * @var string checksum of content
3672     */
3673    protected $_checksum;
3674
3675    /**
3676     * @var int size of content file
3677     */
3678    protected $_fileSize;
3679
3680    /**
3681     * @var object workflow
3682     */
3683    protected $_workflow;
3684
3685    /**
3686     * @var object workflow state
3687     */
3688    protected $_workflowState;
3689
3690    /**
3691     * @var int $_status state
3692     */
3693    protected $_status;
3694
3695    /**
3696     * @var int $_reviewStatus state
3697     */
3698    protected $_reviewStatus;
3699
3700    /**
3701     * @var int $_approvalStatus state
3702     */
3703    protected $_approvalStatus;
3704
3705    /**
3706     * @var int $_receiptStatus state
3707     */
3708    protected $_receiptStatus;
3709
3710    /**
3711     * @var int $_revisionStatus state
3712     */
3713    protected $_revisionStatus;
3714
3715    /**
3716     * @var string date of revision
3717     */
3718    protected $_revisionDate;
3719
3720    /**
3721     * @var array $_readAccessList
3722     */
3723    protected $_readAccessList;
3724
3725    /**
3726     * @var object dms
3727     */
3728    public $_dms;
3729
3730    /**
3731     * Recalculate the status of a document
3732     *
3733     * The methods checks the review and approval status and sets the
3734     * status of the document accordingly.
3735     *
3736     * If status is S_RELEASED and the version has a workflow, then set
3737     * the status to S_IN_WORKFLOW
3738     * If status is S_RELEASED and there are reviewers => set status S_DRAFT_REV
3739     * If status is S_RELEASED or S_DRAFT_REV and there are approvers => set
3740     * status S_DRAFT_APP
3741     * If status is draft and there are no approver and no reviewers => set
3742     * status to S_RELEASED
3743     * The status of a document with the current status S_OBSOLETE, S_REJECTED,
3744     * S_NEEDS_CORRECTION or S_EXPIRED will not be changed unless the parameter
3745     * $ignorecurrentstatus is set to true.
3746     *
3747     * This method may not be called after a negative approval or review to
3748     * recalculated the status, because
3749     * it doesn't take a defeating approval or review into account. This method
3750     * does not set the status to S_REJECTED! It will
3751     * just check for a pending workflow, approval or review and set the status
3752     * accordingly, e.g. after the list of reviewers or appovers has been
3753     * modified. If there is no pending workflow, approval or review the
3754     * status will be set to S_RELEASED.
3755     *
3756     * This method will call {@see SeedDMS_Core_DocumentContent::setStatus()}
3757     * which checks if the status has actually changed. This is, why this
3758     * function can be called at any time without harm to the status log.
3759     * The $initialstatus can be set, to define the status set when no other
3760     * status is set. This happens if the document has no
3761     *
3762     * @param boolean $ignorecurrentstatus ignore the current status and
3763     *        recalculate a new status in any case
3764     * @param object $user the user initiating this method
3765     * @param string $msg message stored in status log when status is set
3766     * @param integer $initialstatus status to be set if no other status is set
3767     */
3768    function verifyStatus($ignorecurrentstatus=false, $user=null, $msg='', $initialstatus=S_RELEASED) { /* {{{ */
3769
3770        unset($this->_status);
3771        $st=$this->getStatus();
3772
3773        /* Documents already obsoleted, rejected or expired will not change
3774         * its status anymore, unless explicitly requested. Be aware, that
3775         * this method has an unsufficient check for negative reviews and
3776         * approvals. A document in status S_REJECTED may become S_RELEASED
3777         * if there is at least one positive review or approval.
3778         */
3779        if (!$ignorecurrentstatus && ($st["status"]==S_OBSOLETE || $st["status"]==S_REJECTED || $st["status"]==S_EXPIRED || $st["status"]==S_NEEDS_CORRECTION)) return $st['status'];
3780
3781        $this->_workflow = null; // force to be reloaded from DB
3782        $hasworkflow = $this->getWorkflow() ? true : false;
3783
3784        /* $pendingReview will be set when there are still open reviews */
3785        $pendingReview=false;
3786        /* $hasReview will be set if there is at least one positiv review */
3787        $hasReview=false;
3788        unset($this->_reviewStatus);  // force to be reloaded from DB
3789        $reviewStatus=$this->getReviewStatus();
3790        if (is_array($reviewStatus) && count($reviewStatus)>0) {
3791            foreach ($reviewStatus as $r){
3792                if ($r["status"]==0){
3793                    $pendingReview=true;
3794                    break;
3795                } elseif($r["status"]==1){
3796                    $hasReview=true;
3797                }
3798            }
3799        }
3800
3801        /* $pendingApproval will be set when there are still open approvals */
3802        $pendingApproval=false;
3803        /* $hasApproval will be set if there is at least one positiv review */
3804        $hasApproval=false;
3805        unset($this->_approvalStatus);  // force to be reloaded from DB
3806        $approvalStatus=$this->getApprovalStatus();
3807        if (is_array($approvalStatus) && count($approvalStatus)>0) {
3808            foreach ($approvalStatus as $a){
3809                if ($a["status"]==0){
3810                    $pendingApproval=true;
3811                    break;
3812                } elseif($a["status"]==1){
3813                    $hasApproval=true;
3814                }
3815            }
3816        }
3817        $pendingRevision=false;
3818        $hasRevision=false;
3819        $needsCorrection=false;
3820        unset($this->_revisionStatus);  // force to be reloaded from DB
3821        $revsisionStatus=$this->getRevisionStatus();
3822        if (is_array($revsisionStatus) && count($revsisionStatus)>0) {
3823            foreach ($revsisionStatus as $a){
3824                if ($a["status"]==0){
3825                    $pendingRevision=true;
3826                    break;
3827                } elseif($a["status"]==1){
3828                    $hasRevision=true;
3829                } elseif($a["status"]==-1){
3830                    $needsCorrection=true;
3831                }
3832            }
3833        }
3834
3835        $ret = false;
3836        /* First check for a running workflow or open reviews, approvals, revisions. */
3837        if ($hasworkflow) { $newstatus = S_IN_WORKFLOW; $ret = $this->setStatus(S_IN_WORKFLOW,$msg,$user); }
3838        elseif ($pendingReview) { $newstatus = S_DRAFT_REV; $ret = $this->setStatus(S_DRAFT_REV,$msg,$user); }
3839        elseif ($pendingApproval) { $newstatus = S_DRAFT_APP; $ret = $this->setStatus(S_DRAFT_APP,$msg,$user); }
3840        elseif ($pendingRevision) { $newstatus = S_IN_REVISION; $ret = $this->setStatus(S_IN_REVISION,$msg,$user); }
3841        /* This point will only be reached if there is no pending workflow, review,
3842         * approval or revision but the current status is one of S_DRAFT_REV,
3843         * S_DRAFT_APP or S_IN_REVISION. This can happen if formely set reviewers,
3844         * approvers, revisors are completly removed. In case of S_DRAFT_REV and
3845         * S_DRAFT_APP the document will go back into its initial status. If a
3846         * positive review or approval was found the document will be released.
3847         * Be aware that negative reviews or approvals are not taken into account,
3848         * because in that case the document must have been rejected before calling
3849         * this function. FIXME: this is a problem if the parameter $ignorecurrentstatus
3850         * was set, because an already rejected document may be released with just
3851         * one positive review or approval disregarding any negative reviews or
3852         * approvals.
3853         * A document in status S_IN_REVISION will be treated differently.
3854         * It takes negative revisions into account!
3855         *
3856         * A document in status S_DRAFT will never go into S_RELEASED and document
3857         * already released will never go back at this point into the given
3858         * initial status, which can only by S_DRAFT or S_RELEASED
3859         */
3860        elseif ($st["status"]!=S_DRAFT && $st["status"]!=S_RELEASED ) {
3861            if($st["status"]==S_DRAFT_REV || $st["status"]==S_DRAFT_APP) {
3862                if($hasReview || $hasApproval) { $newstatus = S_RELEASED; $ret = $this->setStatus(S_RELEASED,$msg,$user); }
3863                else { $newstatus = $initialstatus; $ret = $this->setStatus($initialstatus,$msg,$user); }
3864            } elseif($st["status"]==S_IN_REVISION) {
3865                if($needsCorrection) { $newstatus = S_NEEDS_CORRECTION; $ret = $this->setStatus(S_NEEDS_CORRECTION,$msg,$user); }
3866                else {
3867                    $newstatus = S_RELEASED;
3868                    $ret = $this->finishRevision($user, S_RELEASED, 'Finished revision workflow', $msg);
3869                }
3870            } elseif($st["status"]==S_EXPIRED) {
3871                $newstatus = S_RELEASED; $ret = $this->setStatus(S_RELEASED,$msg,$user);
3872            } elseif($st["status"]==S_IN_WORKFLOW) {
3873                $newstatus = $initialstatus; $ret = $this->setStatus($initialstatus,$msg,$user);
3874            }
3875        }
3876
3877        return $ret ? $newstatus : $ret;
3878    } /* }}} */
3879
3880    public function __construct($id, $document, $version, $comment, $date, $userID, $dir, $orgFileName, $fileType, $mimeType, $fileSize=0, $checksum='', $revisionDate=null) { /* {{{ */
3881        parent::__construct($id);
3882        $this->_document = $document;
3883        $this->_version = (int) $version;
3884        $this->_comment = $comment;
3885        $this->_date = (int) $date;
3886        $this->_userID = (int) $userID;
3887        $this->_user = null;
3888        $this->_dir = $dir;
3889        $this->_orgFileName = $orgFileName;
3890        $this->_fileType = $fileType;
3891        $this->_mimeType = $mimeType;
3892        $this->_dms = $document->getDMS();
3893        if(!$fileSize) {
3894            if($storage = $this->_dms->getStorage()) {
3895                $filesize = $storage->getContentFilesize($document, $this);
3896            } else {
3897                $this->_fileSize = SeedDMS_Core_File::fileSize($this->_dms->contentDir . $this->getPath());
3898            }
3899        } else {
3900            $this->_fileSize = (int) $fileSize;
3901        }
3902        $this->_checksum = $checksum;
3903        $this->_workflow = null;
3904        $this->_workflowState = null;
3905        $this->_readAccessList = null;
3906        $this->_revisionDate = $revisionDate;
3907    } /* }}} */
3908
3909    /**
3910     * Return an document content by its id
3911     *
3912     * @param integer $id id of document
3913     * @param SeedDMS_Core_DMS $dms
3914     * @return bool|SeedDMS_Core_DocumentContent instance of SeedDMS_Core_DocumentContent
3915     * if document content exists, null if document does not exist, false in case of error
3916     */
3917    public static function getInstance($id, $dms) { /* {{{ */
3918        $db = $dms->getDB();
3919
3920        $queryStr = "SELECT * FROM `tblDocumentContent` WHERE `id` = " . (int) $id;
3921        $resArr = $db->getResultArray($queryStr);
3922        if (is_bool($resArr) && $resArr == false)
3923            return false;
3924        if (count($resArr) != 1)
3925            return null;
3926        $row = $resArr[0];
3927
3928        $classname = $dms->getClassname('documentcontent');
3929        $user = $dms->getLoggedInUser();
3930        $document = $dms->getDocument($row['document']);
3931        $document->setDMS($dms);
3932        /** @var SeedDMS_Core_DocumentContent $documentcontent */
3933        $content = new $classname($row["id"], $document, $row["version"], $row["comment"], $row["date"], $row["createdBy"], $row["dir"], $row["orgFileName"], $row["fileType"], $row["mimeType"], $row['fileSize'], $row['checksum'], $row['revisiondate']);
3934        if($user) {
3935            if($content->getAccessMode($user) >= M_READ)
3936                return $content;
3937        } else {
3938            return $content;
3939        }
3940        return null;
3941    } /* }}} */
3942
3943    /**
3944     * Check if this object is of type 'documentcontent'.
3945     *
3946     * @param string $type type of object
3947     */
3948    public function isType($type) { /* {{{ */
3949        return $type == 'documentcontent';
3950    } /* }}} */
3951
3952    public function getVersion() { return $this->_version; }
3953    public function getComment() { return $this->_comment; }
3954    public function getDate() { return $this->_date; }
3955    public function getOriginalFileName() { return $this->_orgFileName; }
3956    public function getFileType() { return $this->_fileType; }
3957    public function getFileName(){ return $this->_version . $this->_fileType; }
3958    /**
3959     * getDir and the corresponding database table field are deprecated
3960     */
3961    private function __getDir() { return $this->_dir; }
3962    public function getMimeType() { return $this->_mimeType; }
3963    public function getRevisionDate() { return $this->_revisionDate; }
3964    public function getDocument() { return $this->_document; }
3965
3966    public function getUser() { /* {{{ */
3967        if (!isset($this->_user))
3968            $this->_user = $this->_document->getDMS()->getUser($this->_userID);
3969        return $this->_user;
3970    } /* }}} */
3971
3972    /**
3973     * Return path of file on disk relative to the content directory
3974     *
3975     * Since version 5.1.13 a single '.' in the fileType will be skipped.
3976     * On Windows a file named 'name.' will be saved as 'name' but the fileType
3977     * will contain the a single '.'.
3978     *
3979     * @return string path of file on disc
3980     */
3981    public function getPath() { return $this->_document->getDir() . $this->_version . $this->_fileType; }
3982
3983    function setRevisionDate($date = false) { /* {{{ */
3984        $db = $this->_document->getDMS()->getDB();
3985
3986        if(!$date)
3987            $queryStr = "UPDATE `tblDocumentContent` SET `revisiondate` = null WHERE `document` = " . $this->_document->getID() .    " AND `version` = " . $this->_version;
3988        elseif($date == 'now')
3989            $queryStr = "UPDATE `tblDocumentContent` SET `revisiondate` = ".$db->getCurrentDatetime()." WHERE `document` = " . $this->_document->getID() .    " AND `version` = " . $this->_version;
3990        else
3991            $queryStr = "UPDATE `tblDocumentContent` SET `revisiondate` = ".$db->qstr($date)." WHERE `document` = " . $this->_document->getID() .    " AND `version` = " . $this->_version;
3992        if (!$db->getResult($queryStr))
3993            return false;
3994
3995        $this->_revisionDate = $date;
3996
3997        return true;
3998    } /* }}} */
3999
4000    /**
4001     * Set upload date of document content
4002     *
4003     * @param string $date date must be a timestamp or in the format 'Y-m-d H:i:s'
4004     *
4005     * @return boolean true on success, otherwise false
4006     */
4007    public function setDate($date = false) { /* {{{ */
4008        $db = $this->_document->getDMS()->getDB();
4009
4010        if(!$date)
4011            $date = time();
4012        else {
4013            if(is_string($date) && SeedDMS_Core_DMS::checkDate($date, 'Y-m-d H:i:s')) {
4014                $date = strtotime($date);
4015            } elseif(is_numeric($date))
4016                $date = (int) $date;
4017            else
4018                return false;
4019        }
4020
4021        $queryStr = "UPDATE `tblDocumentContent` SET `date` = ". $date." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
4022        if (!$db->getResult($queryStr))
4023            return false;
4024
4025        $this->_date = $date;
4026
4027        return true;
4028    } /* }}} */
4029
4030    public function getFileSize() { /* {{{ */
4031        return $this->_fileSize;
4032    } /* }}} */
4033
4034    /**
4035     * Set file size by reading the file
4036     */
4037    public function setFileSize() { /* {{{ */
4038        if($storage = $this->_dms->getStorage()) {
4039            $filesize = $storage->getContentFilesize($this->_document, $this);
4040        } else {
4041            $filesize = SeedDMS_Core_File::fileSize($this->_dms->contentDir . $this->_document->getDir() . $this->getFileName());
4042        }
4043        if($filesize === false)
4044            return false;
4045
4046        $db = $this->_document->getDMS()->getDB();
4047        $queryStr = "UPDATE `tblDocumentContent` SET `fileSize` = ".$filesize." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
4048        if (!$db->getResult($queryStr))
4049            return false;
4050        $this->_fileSize = $filesize;
4051
4052        return true;
4053    } /* }}} */
4054
4055    public function getChecksum() { /* {{{ */
4056        return $this->_checksum;
4057    } /* }}} */
4058
4059    public function getRealChecksum() { /* {{{ */
4060        if($storage = $this->_dms->getStorage()) {
4061            $checksum = $storage->getContentChecksum($this->_document, $this);
4062        } else {
4063            $checksum = SeedDMS_Core_File::checksum($this->_dms->contentDir . $this->getPath());
4064        }
4065        return $checksum;
4066    } /* }}} */
4067
4068    /**
4069     * Set checksum by reading the file
4070     */
4071    public function setChecksum() { /* {{{ */
4072        if($storage = $this->_dms->getStorage()) {
4073            $checksum = $storage->getContentChecksum($this->_document, $this);
4074        } else {
4075            $checksum = SeedDMS_Core_File::checksum($this->_dms->contentDir . $this->_document->getDir() . $this->getFileName());
4076        }
4077        if($checksum === false)
4078            return false;
4079
4080        $db = $this->_document->getDMS()->getDB();
4081        $queryStr = "UPDATE `tblDocumentContent` SET `checksum` = ".$db->qstr($checksum)." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
4082        if (!$db->getResult($queryStr))
4083            return false;
4084        $this->_checksum = $checksum;
4085
4086        return true;
4087    } /* }}} */
4088
4089    public function getRealMimeType() { /* {{{ */
4090        if($storage = $this->_dms->getStorage()) {
4091            $mimetype = $storage->getContentMimetype($this->_document, $this);
4092        } else {
4093            $mimetype = SeedDMS_Core_File::mimetype($this->_dms->contentDir . $this->getPath());
4094        }
4095        return $mimetype;
4096    } /* }}} */
4097
4098    /**
4099     * Set file type by evaluating the mime type
4100     */
4101    public function setFileType() { /* {{{ */
4102        $mimetype = $this->getMimeType();
4103
4104        $expect = SeedDMS_Core_File::fileExtension($mimetype);
4105        if($expect && '.'.$expect != $this->_fileType) {
4106            $db = $this->_document->getDMS()->getDB();
4107            $db->startTransaction();
4108            $queryStr = "UPDATE `tblDocumentContent` SET `fileType`='.".$expect."' WHERE `id` =   ". $this->_id;
4109            $res = $db->getResult($queryStr);
4110            if ($res) {
4111                if($storage = $this->_dms->getStorage()) {
4112                    $err = $storage->setFileType($this->_document, $this, '.'.$expect);
4113                } else {
4114                    $err = SeedDMS_Core_File::renameFile($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType, $this->_dms->contentDir.$this->_document->getDir() . $this->_version . '.' . $expect);
4115                }
4116                if(!$err) {
4117                    $db->rollbackTransaction();
4118                } else {
4119                    $this->_fileType = '.'.$expect;
4120                    $db->commitTransaction();
4121                    return true;
4122                }
4123            } else {
4124                $db->rollbackTransaction();
4125            }
4126        }
4127
4128        return false;
4129    } /* }}} */
4130
4131    public function setMimeType($newMimetype) { /* {{{ */
4132        $db = $this->_document->getDMS()->getDB();
4133
4134        if(!$newMimetype)
4135            return false;
4136
4137        $newMimetype = trim($newMimetype);
4138
4139        if(!$newMimetype)
4140            return false;
4141
4142        $queryStr = "UPDATE `tblDocumentContent` SET `mimeType` = ".$db->qstr($newMimetype)." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
4143        if (!$db->getResult($queryStr))
4144            return false;
4145
4146        $this->_mimeType = $newMimetype;
4147
4148        return true;
4149    } /* }}} */
4150
4151    public function setComment($newComment) { /* {{{ */
4152        $db = $this->_document->getDMS()->getDB();
4153
4154        /* Check if 'onPreSetVersionComment' callback is set */
4155        if(isset($this->_dms->callbacks['onPreSetVersionComment'])) {
4156            foreach($this->_dms->callbacks['onPreSetVersionComment'] as $callback) {
4157                $ret = call_user_func($callback[0], $callback[1], $this, $newComment);
4158                if(is_bool($ret))
4159                    return $ret;
4160            }
4161        }
4162
4163        $queryStr = "UPDATE `tblDocumentContent` SET `comment` = ".$db->qstr($newComment)." WHERE `document` = " . $this->_document->getID() . " AND `version` = " . $this->_version;
4164        if (!$db->getResult($queryStr))
4165            return false;
4166
4167        $this->_comment = $newComment;
4168
4169        /* Check if 'onPostSetVersionComment' callback is set */
4170        if(isset($this->_dms->callbacks['onPostSetVersionComment'])) {
4171            foreach($this->_dms->callbacks['onPostSetVersionComment'] as $callback) {
4172                $ret = call_user_func($callback[0], $callback[1], $this, $oldComment);
4173                if(is_bool($ret))
4174                    return $ret;
4175            }
4176        }
4177
4178        return true;
4179    } /* }}} */
4180
4181    /**
4182     * Get the latest status of the content
4183     *
4184     * The status of the content reflects its current review, approval or workflow
4185     * state. A status can be a negative or positive number or 0. A negative
4186     * numbers indicate a missing approval, review or an obsolete content.
4187     * Positive numbers indicate some kind of approval or workflow being
4188     * active, but not necessarily a release.
4189     * S_DRAFT_REV, 0
4190     * S_DRAFT_APP, 1
4191     * S_RELEASED, 2
4192     * S_IN_WORKFLOW, 3
4193     * S_IN_REVISION, 4
4194     * S_REJECTED, -1
4195     * S_OBSOLETE, -2
4196     * S_EXPIRED, -3
4197     * When a content is inserted and does not need approval nor review,
4198     * then its status is set to S_RELEASED immediately. Any change of
4199     * the status is monitored in the table tblDocumentStatusLog. This
4200     * function will always return the latest entry for the content.
4201     *
4202     * @return array latest record from tblDocumentStatusLog
4203     */
4204    public function getStatus($limit=1) { /* {{{ */
4205        $db = $this->_document->getDMS()->getDB();
4206
4207        if (!is_numeric($limit)) return false;
4208
4209        // Retrieve the current overall status of the content represented by
4210        // this object.
4211        if (!isset($this->_status)) {
4212            $queryStr=
4213                "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`status`, ".
4214                "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ".
4215                "`tblDocumentStatusLog`.`userID` ".
4216                "FROM `tblDocumentStatus` ".
4217                "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ".
4218                "WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' ".
4219                "AND `tblDocumentStatus`.`version` = '". $this->_version ."' ".
4220                "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC LIMIT ".(int) $limit;
4221
4222            $res = $db->getResultArray($queryStr);
4223            if (is_bool($res) && !$res)
4224                return false;
4225            if (count($res)!=1)
4226                return false;
4227            $this->_status = $res[0];
4228        }
4229        return $this->_status;
4230    } /* }}} */
4231
4232    /**
4233     * Get current and former states of the document content
4234     *
4235     * @param integer $limit if not set all log entries will be returned
4236     * @return array list of status changes
4237     */
4238    public function getStatusLog($limit=0) { /* {{{ */
4239        $db = $this->_document->getDMS()->getDB();
4240
4241        if (!is_numeric($limit)) return false;
4242
4243        $queryStr=
4244            "SELECT `tblDocumentStatus`.*, `tblDocumentStatusLog`.`status`, ".
4245            "`tblDocumentStatusLog`.`comment`, `tblDocumentStatusLog`.`date`, ".
4246            "`tblDocumentStatusLog`.`userID` ".
4247            "FROM `tblDocumentStatus` ".
4248            "LEFT JOIN `tblDocumentStatusLog` USING (`statusID`) ".
4249            "WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' ".
4250            "AND `tblDocumentStatus`.`version` = '". $this->_version ."' ".
4251            "ORDER BY `tblDocumentStatusLog`.`statusLogID` DESC ";
4252        if($limit)
4253            $queryStr .= "LIMIT ".(int) $limit;
4254
4255        $res = $db->getResultArray($queryStr);
4256        if (is_bool($res) && !$res)
4257            return false;
4258
4259        return $res;
4260    } /* }}} */
4261
4262    /**
4263     * Set the status of the content
4264     *
4265     * Setting the status means to add another entry into the table
4266     * tblDocumentStatusLog. The method returns also false if the status
4267     * is already set on the value passed to the method.
4268     *
4269     * @param integer $status     new status of content
4270     * @param string  $comment    comment for this status change
4271     * @param object  $updateUser user initiating the status change
4272     * @param string  $date       date in the format 'Y-m-d H:i:s'
4273     *
4274     * @return boolean true on success, otherwise false
4275     */
4276    public function setStatus(int $status, string $comment, $updateUser, $date='') { /* {{{ */
4277        $db = $this->_document->getDMS()->getDB();
4278
4279        if (!is_numeric($status)) return false;
4280
4281        /* return an error if $updateuser is not set */
4282        if(!$updateUser || !$updateUser->isType('user'))
4283            return false;
4284
4285        // If the supplied value lies outside of the accepted range, return an
4286        // error.
4287        if ($status < S_LOWEST_STATUS || $status > S_HIGHEST_STATUS) {
4288            return false;
4289        }
4290
4291        // Retrieve the current overall status of the content represented by
4292        // this object, if it hasn't been done already.
4293        if (!isset($this->_status)) {
4294            $this->getStatus();
4295        }
4296        if ($this->_status["status"]==$status) {
4297            return true;
4298        }
4299        if($date) {
4300            if(!SeedDMS_Core_DMS::checkDate($date, 'Y-m-d H:i:s'))
4301                return false;
4302            $ddate = $db->qstr($date);
4303        } else
4304            $ddate = $db->getCurrentDatetime();
4305        $db->startTransaction();
4306        $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ".
4307            "VALUES ('". $this->_status["statusID"] ."', '". (int) $status ."', ".$db->qstr($comment).", ".$ddate.", '". $updateUser->getID() ."')";
4308        $res = $db->getResult($queryStr);
4309        if (is_bool($res) && !$res) {
4310            $db->rollbackTransaction();
4311            return false;
4312        }
4313
4314        /* Check if 'onSetStatus' callback is set */
4315        if(isset($this->_dms->callbacks['onSetStatus'])) {
4316            foreach($this->_dms->callbacks['onSetStatus'] as $callback) {
4317                $ret = call_user_func($callback[0], $callback[1], $this, $updateUser, $this->_status["status"], $status);
4318                if(is_bool($ret)) {
4319                    unset($this->_status);
4320                    if($ret)
4321                        $db->commitTransaction();
4322                    else
4323                        $db->rollbackTransaction();
4324                    return $ret;
4325                }
4326            }
4327        }
4328
4329        $db->commitTransaction();
4330        unset($this->_status);
4331        return true;
4332    } /* }}} */
4333
4334    /**
4335     * Rewrites the complete status log
4336     *
4337     * Attention: this function is highly dangerous.
4338     * It removes an existing status log and rewrites it.
4339     * This method was added for importing an xml dump.
4340     *
4341     * @param array $statuslog new status log with the newest log entry first.
4342     * @return boolean true on success, otherwise false
4343     */
4344    public function rewriteStatusLog($statuslog) { /* {{{ */
4345        $db = $this->_document->getDMS()->getDB();
4346
4347        $queryStr= "SELECT `tblDocumentStatus`.* FROM `tblDocumentStatus` WHERE `tblDocumentStatus`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentStatus`.`version` = '". $this->_version ."' ";
4348        $res = $db->getResultArray($queryStr);
4349        if (is_bool($res) && !$res)
4350            return false;
4351
4352        $statusID = $res[0]['statusID'];
4353
4354        $db->startTransaction();
4355
4356        /* First, remove the old entries */
4357        $queryStr = "DELETE FROM `tblDocumentStatusLog` WHERE `statusID`=".$statusID;
4358        if (!$db->getResult($queryStr)) {
4359            $db->rollbackTransaction();
4360            return false;
4361        }
4362
4363        /* Second, insert the new entries */
4364        $statuslog = array_reverse($statuslog);
4365        foreach($statuslog as $log) {
4366            if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
4367                $db->rollbackTransaction();
4368                return false;
4369            }
4370            $queryStr = "INSERT INTO `tblDocumentStatusLog` (`statusID`, `status`, `comment`, `date`, `userID`) ".
4371                "VALUES ('".$statusID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".$log['user']->getID().")";
4372            if (!$db->getResult($queryStr)) {
4373                $db->rollbackTransaction();
4374                return false;
4375            }
4376        }
4377
4378        $db->commitTransaction();
4379        return true;
4380    } /* }}} */
4381
4382
4383    /**
4384     * Returns the access mode similar to a document
4385     *
4386     * There is no real access mode for document content, so this is more
4387     * like a virtual access mode, derived from the status of the document
4388     * content. The function checks if {@see SeedDMS_Core_DMS::noReadForStatus}
4389     * contains the status of the version and returns M_NONE if it exists and
4390     * the user is not involved in a workflow or review/approval/revision.
4391     * This method is called by all functions that returns the content e.g.
4392     * {@see SeedDMS_Core_Document::getLatestContent()}
4393     * It is also used by {@see SeedDMS_Core_Document::getAccessMode()} to
4394     * prevent access on the whole document if there is no accessible version.
4395     *
4396     * FIXME: This method only works propperly if $u is the currently logged in
4397     * user, because noReadForStatus will be set for this user.
4398     * FIXED: instead of using $dms->noReadForStatus it is take from the user's role
4399     *
4400     * @param object $u user
4401     * @return integer either M_NONE or M_READ
4402     */
4403    public function getAccessMode($u) { /* {{{ */
4404        $dms = $this->_document->getDMS();
4405
4406        /* Check if 'onCheckAccessDocumentContent' callback is set */
4407        if(isset($this->_dms->callbacks['onCheckAccessDocumentContent'])) {
4408            foreach($this->_dms->callbacks['onCheckAccessDocumentContent'] as $callback) {
4409                if(($ret = call_user_func($callback[0], $callback[1], $this, $u)) > 0) {
4410                    return $ret;
4411                }
4412            }
4413        }
4414
4415//        return M_READ;
4416
4417        if(!$u)
4418            return M_NONE;
4419
4420        /* If read access isn't further restricted by status, than grant read access */
4421        /* Old code
4422        if(!$dms->noReadForStatus)
4423            return M_READ;
4424        $noReadForStatus = $dms->noReadForStatus;
4425        */
4426        $noReadForStatus = $u->getRole()->getNoAccess();
4427        if(!$noReadForStatus)
4428            return M_READ;
4429
4430        /* If the current status is not in list of status without read access, then grant read access */
4431        if(!in_array($this->getStatus()['status'], $noReadForStatus))
4432            return M_READ;
4433
4434        /* Administrators have unrestricted access */
4435        if ($u->isAdmin()) return M_READ;
4436
4437        /* The owner of the document has unrestricted access */
4438        $owner = $this->_document->getOwner();
4439        if ($u->getID() == $owner->getID()) return M_READ;
4440
4441        /* Read/Write access on the document will also grant access on the version */
4442        if($this->_document->getAccessMode($u) >= M_READWRITE) return M_READ;
4443
4444        /* At this point the current status is in the list of status without read access.
4445         * The only way to still gain read access is, if the user is involved in the
4446         * process, e.g. is a reviewer, approver or an active person in the workflow.
4447         */
4448        $s = $this->getStatus();
4449        switch($s['status']) {
4450        case S_DRAFT_REV:
4451            $status = $this->getReviewStatus();
4452            foreach ($status as $r) {
4453                if($r['status'] != -2) // Check if reviewer was removed
4454                    switch ($r["type"]) {
4455                    case 0: // Reviewer is an individual.
4456                        if($u->getId() == $r["required"])
4457                            return M_READ;
4458                        break;
4459                    case 1: // Reviewer is a group.
4460                        $required = $dms->getGroup($r["required"]);
4461                        if (is_object($required) && $required->isMember($u))
4462                            return M_READ;
4463                        break;
4464                    }
4465            }
4466            break;
4467        case S_DRAFT_APP:
4468            $status = $this->getApprovalStatus();
4469            foreach ($status as $r) {
4470                if($r['status'] != -2) // Check if approver was removed
4471                    switch ($r["type"]) {
4472                    case 0: // Reviewer is an individual.
4473                        if($u->getId() == $r["required"])
4474                            return M_READ;
4475                        break;
4476                    case 1: // Reviewer is a group.
4477                        $required = $dms->getGroup($r["required"]);
4478                        if (is_object($required) && $required->isMember($u))
4479                            return M_READ;
4480                        break;
4481                    }
4482            }
4483            break;
4484        case S_RELEASED:
4485            break;
4486        case S_IN_WORKFLOW:
4487            if(!$this->_workflow)
4488                $this->getWorkflow();
4489
4490            if($this->_workflow) {
4491                if (!$this->_workflowState)
4492                    $this->getWorkflowState();
4493                $transitions = $this->_workflow['workflow']->getNextTransitions($this->_workflowState);
4494                foreach($transitions as $transition) {
4495                    if($this->triggerWorkflowTransitionIsAllowed($u, $transition))
4496                        return M_READ;
4497                }
4498            }
4499            break;
4500        case S_IN_REVISION:
4501            $status = $this->getRevisionStatus();
4502            foreach ($status as $r) {
4503                if($r['status'] != -2) // Check if reviewer was removed
4504                    switch ($r["type"]) {
4505                    case 0: // Revisor is an individual.
4506                        if($u->getId() == $r["required"])
4507                            return M_READ;
4508                        break;
4509                    case 1: // Revisor is a group.
4510                        $required = $dms->getGroup($r["required"]);
4511                        if (is_object($required) && $required->isMember($u))
4512                            return M_READ;
4513                        break;
4514                    }
4515            }
4516            break;
4517        case S_REJECTED:
4518            break;
4519        case S_OBSOLETE:
4520            break;
4521        case S_EXPIRED:
4522            break;
4523        }
4524
4525        return M_NONE;
4526    } /* }}} */
4527
4528    /**
4529     * Return a list of all reviewers separated by individuals and groups
4530     * This list will not take the review log into account. Therefore it
4531     * can contain reviewers which has actually been deleted as a reviewer.
4532     *
4533     * @return array|bool|null
4534     */
4535    public function getReviewers() { /* {{{ */
4536        $dms = $this->_document->getDMS();
4537        $db = $dms->getDB();
4538
4539        $queryStr=
4540            "SELECT * FROM `tblDocumentReviewers` WHERE `version`='".$this->_version
4541            ."' AND `documentID` = '". $this->_document->getID() ."' ";
4542
4543        $recs = $db->getResultArray($queryStr);
4544        if (is_bool($recs))
4545            return false;
4546        $reviewers = array('i'=>array(), 'g'=>array());
4547        foreach($recs as $rec) {
4548            if($rec['type'] == 0) {
4549                if($u = $dms->getUser($rec['required']))
4550                    $reviewers['i'][] = $u;
4551            } elseif($rec['type'] == 1) {
4552                if($g = $dms->getGroup($rec['required']))
4553                    $reviewers['g'][] = $g;
4554            }
4555        }
4556        return $reviewers;
4557    } /* }}} */
4558
4559    /**
4560     * Get the current review status of the document content
4561     * The review status is a list of reviewers and its current status
4562     *
4563     * @param integer $limit the number of recent status changes per reviewer
4564     * @return array list of review status
4565     */
4566    public function getReviewStatus($limit=1) { /* {{{ */
4567        $db = $this->_document->getDMS()->getDB();
4568
4569        if (!is_numeric($limit)) return false;
4570
4571        // Retrieve the current status of each assigned reviewer for the content
4572        // represented by this object.
4573        // FIXME: caching was turned off to make list of review log in ViewDocument
4574        // possible
4575        if (1 || !isset($this->_reviewStatus)) {
4576            /* First get a list of all reviews for this document content */
4577            $queryStr=
4578                "SELECT `reviewID` FROM `tblDocumentReviewers` WHERE `version`='".$this->_version
4579                ."' AND `documentID` = '". $this->_document->getID() ."' ";
4580            $recs = $db->getResultArray($queryStr);
4581            if (is_bool($recs) && !$recs)
4582                return false;
4583            $this->_reviewStatus = array();
4584            if($recs) {
4585                foreach($recs as $rec) {
4586                    $queryStr=
4587                        "SELECT `tblDocumentReviewers`.*, `tblDocumentReviewLog`.`reviewLogID`, `tblDocumentReviewLog`.`status`, ".
4588                        "`tblDocumentReviewLog`.`comment`, `tblDocumentReviewLog`.`date`, ".
4589                        "`tblDocumentReviewLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ".
4590                        "FROM `tblDocumentReviewers` ".
4591                        "LEFT JOIN `tblDocumentReviewLog` USING (`reviewID`) ".
4592                        "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentReviewers`.`required`".
4593                        "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentReviewers`.`required`".
4594                        "WHERE `tblDocumentReviewers`.`reviewID` = '". $rec['reviewID'] ."' ".
4595                        "ORDER BY `tblDocumentReviewLog`.`reviewLogID` DESC LIMIT ".(int) $limit;
4596
4597                    $res = $db->getResultArray($queryStr);
4598                    if (is_bool($res) && !$res) {
4599                        unset($this->_reviewStatus);
4600                        return false;
4601                    }
4602                    foreach($res as &$t) {
4603                        $filename = $this->_dms->contentDir . $this->_document->getDir().'r'.$t['reviewLogID'];
4604                        if(SeedDMS_Core_File::file_exists($filename))
4605                            $t['file'] = $filename;
4606                        else
4607                            $t['file'] = '';
4608                    }
4609                    $this->_reviewStatus = array_merge($this->_reviewStatus, $res);
4610                }
4611            }
4612        }
4613        return $this->_reviewStatus;
4614    } /* }}} */
4615
4616    /**
4617     * Get the latest entries from the review log of the document content
4618     *
4619     * @param integer $limit the number of log entries returned, defaults to 1
4620     * @return array list of review log entries
4621     */
4622    public function getReviewLog($limit=1) { /* {{{ */
4623        $db = $this->_document->getDMS()->getDB();
4624
4625        if (!is_numeric($limit)) return false;
4626
4627        $queryStr=
4628            "SELECT * FROM `tblDocumentReviewLog` LEFT JOIN `tblDocumentReviewers` ON  `tblDocumentReviewLog`.`reviewID` = `tblDocumentReviewers`.`reviewID` WHERE `version`='".$this->_version
4629            ."' AND `documentID` = '". $this->_document->getID() ."' "
4630            ."ORDER BY `tblDocumentReviewLog`.`reviewLogID` DESC LIMIT ".(int) $limit;
4631        $recs = $db->getResultArray($queryStr);
4632        if (is_bool($recs) && !$recs)
4633            return false;
4634        return($recs);
4635    } /* }}} */
4636
4637    /**
4638     * Rewrites the complete review log
4639     *
4640     * Attention: this function is highly dangerous.
4641     * It removes an existing review log and rewrites it.
4642     * This method was added for importing an xml dump.
4643     *
4644     * @param array $reviewlog new status log with the newest log entry first.
4645     * @return boolean true on success, otherwise false
4646     */
4647    public function rewriteReviewLog($reviewers) { /* {{{ */
4648        $db = $this->_document->getDMS()->getDB();
4649
4650        $queryStr= "SELECT `tblDocumentReviewers`.* FROM `tblDocumentReviewers` WHERE `tblDocumentReviewers`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentReviewers`.`version` = '". $this->_version ."' ";
4651        $res = $db->getResultArray($queryStr);
4652        if (is_bool($res) && !$res)
4653            return false;
4654
4655        $db->startTransaction();
4656
4657        if($res) {
4658            foreach($res as $review) {
4659                $reviewID = $review['reviewID'];
4660
4661                /* First, remove the old entries */
4662                $queryStr = "DELETE FROM `tblDocumentReviewLog` WHERE `reviewID`=".$reviewID;
4663                if (!$db->getResult($queryStr)) {
4664                    $db->rollbackTransaction();
4665                    return false;
4666                }
4667
4668                $queryStr = "DELETE FROM `tblDocumentReviewers` WHERE `reviewID`=".$reviewID;
4669                if (!$db->getResult($queryStr)) {
4670                    $db->rollbackTransaction();
4671                    return false;
4672                }
4673            }
4674        }
4675
4676        /* Second, insert the new entries */
4677        foreach($reviewers as $review) {
4678            $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ".
4679                "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$review['type'] .", ".(is_object($review['required']) ? $review['required']->getID() : (int) $review['required']).")";
4680            if (!$db->getResult($queryStr)) {
4681                $db->rollbackTransaction();
4682                return false;
4683            }
4684            $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID');
4685            $reviewlog = array_reverse($review['logs']);
4686            foreach($reviewlog as $log) {
4687                if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
4688                    $db->rollbackTransaction();
4689                    return false;
4690                }
4691                $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
4692                    "VALUES ('".$reviewID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")";
4693                if (!$db->getResult($queryStr)) {
4694                    $db->rollbackTransaction();
4695                    return false;
4696                }
4697                $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
4698                if(!empty($log['file'])) {
4699                    SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID);
4700                }
4701            }
4702        }
4703
4704        $db->commitTransaction();
4705        return true;
4706    } /* }}} */
4707
4708    /**
4709     * Return a list of all approvers separated by individuals and groups
4710     * This list will not take the approval log into account. Therefore it
4711     * can contain approvers which has actually been deleted as an approver.
4712     *
4713     * @return array|bool|null
4714     */
4715    public function getApprovers() { /* {{{ */
4716        $dms = $this->_document->getDMS();
4717        $db = $dms->getDB();
4718
4719        $queryStr=
4720            "SELECT * FROM `tblDocumentApprovers` WHERE `version`='".$this->_version
4721            ."' AND `documentID` = '". $this->_document->getID() ."' ";
4722
4723        $recs = $db->getResultArray($queryStr);
4724        if (is_bool($recs))
4725            return false;
4726        $approvers = array('i'=>array(), 'g'=>array());
4727        foreach($recs as $rec) {
4728            if($rec['type'] == 0) {
4729                if($u = $dms->getUser($rec['required']))
4730                    $approvers['i'][] = $u;
4731            } elseif($rec['type'] == 1) {
4732                if($g = $dms->getGroup($rec['required']))
4733                    $approvers['g'][] = $g;
4734            }
4735        }
4736        return $approvers;
4737    } /* }}} */
4738
4739    /**
4740     * Get the current approval status of the document content
4741     * The approval status is a list of approvers and its current status
4742     *
4743     * @param integer $limit the number of recent status changes per approver
4744     * @return array list of approval status
4745     */
4746    public function getApprovalStatus($limit=1) { /* {{{ */
4747        $db = $this->_document->getDMS()->getDB();
4748
4749        if (!is_numeric($limit)) return false;
4750
4751        // Retrieve the current status of each assigned approver for the content
4752        // represented by this object.
4753        // FIXME: caching was turned off to make list of approval log in ViewDocument
4754        // possible
4755        if (1 || !isset($this->_approvalStatus)) {
4756            /* First get a list of all approvals for this document content */
4757            $queryStr=
4758                "SELECT `approveID` FROM `tblDocumentApprovers` WHERE `version`='".$this->_version
4759                ."' AND `documentID` = '". $this->_document->getID() ."' ";
4760            $recs = $db->getResultArray($queryStr);
4761            if (is_bool($recs) && !$recs)
4762                return false;
4763            $this->_approvalStatus = array();
4764            if($recs) {
4765                foreach($recs as $rec) {
4766                    $queryStr=
4767                        "SELECT `tblDocumentApprovers`.*, `tblDocumentApproveLog`.`approveLogID`, `tblDocumentApproveLog`.`status`, ".
4768                        "`tblDocumentApproveLog`.`comment`, `tblDocumentApproveLog`.`date`, ".
4769                        "`tblDocumentApproveLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ".
4770                        "FROM `tblDocumentApprovers` ".
4771                        "LEFT JOIN `tblDocumentApproveLog` USING (`approveID`) ".
4772                        "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentApprovers`.`required` ".
4773                        "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentApprovers`.`required`".
4774                        "WHERE `tblDocumentApprovers`.`approveID` = '". $rec['approveID'] ."' ".
4775                        "ORDER BY `tblDocumentApproveLog`.`approveLogID` DESC LIMIT ".(int) $limit;
4776
4777                    $res = $db->getResultArray($queryStr);
4778                    if (is_bool($res) && !$res) {
4779                        unset($this->_approvalStatus);
4780                        return false;
4781                    }
4782                    foreach($res as &$t) {
4783                        $filename = $this->_dms->contentDir . $this->_document->getDir().'a'.$t['approveLogID'];
4784                        if(SeedDMS_Core_File::file_exists($filename))
4785                            $t['file'] = $filename;
4786                        else
4787                            $t['file'] = '';
4788                    }
4789                    $this->_approvalStatus = array_merge($this->_approvalStatus, $res);
4790                }
4791            }
4792        }
4793        return $this->_approvalStatus;
4794    } /* }}} */
4795
4796    /**
4797     * Get the latest entries from the approval log of the document content
4798     *
4799     * @param integer $limit the number of log entries returned, defaults to 1
4800     * @return array list of approval log entries
4801     */
4802    public function getApproveLog($limit=1) { /* {{{ */
4803        $db = $this->_document->getDMS()->getDB();
4804
4805        if (!is_numeric($limit)) return false;
4806
4807        $queryStr=
4808            "SELECT * FROM `tblDocumentApproveLog` LEFT JOIN `tblDocumentApprovers` ON  `tblDocumentApproveLog`.`approveID` = `tblDocumentApprovers`.`approveID` WHERE `version`='".$this->_version
4809            ."' AND `documentID` = '". $this->_document->getID() ."' "
4810            ."ORDER BY `tblDocumentApproveLog`.`approveLogID` DESC LIMIT ".(int) $limit;
4811        $recs = $db->getResultArray($queryStr);
4812        if (is_bool($recs) && !$recs)
4813            return false;
4814        return($recs);
4815    } /* }}} */
4816
4817    /**
4818     * Rewrites the complete approval log
4819     *
4820     * Attention: this function is highly dangerous.
4821     * It removes an existing review log and rewrites it.
4822     * This method was added for importing an xml dump.
4823     *
4824     * @param array $reviewlog new status log with the newest log entry first.
4825     * @return boolean true on success, otherwise false
4826     */
4827    public function rewriteApprovalLog($reviewers) { /* {{{ */
4828        $db = $this->_document->getDMS()->getDB();
4829
4830        $queryStr= "SELECT `tblDocumentApprovers`.* FROM `tblDocumentApprovers` WHERE `tblDocumentApprovers`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentApprovers`.`version` = '". $this->_version ."' ";
4831        $res = $db->getResultArray($queryStr);
4832        if (is_bool($res) && !$res)
4833            return false;
4834
4835        $db->startTransaction();
4836
4837        if($res) {
4838            foreach($res as $review) {
4839                $reviewID = $review['reviewID'];
4840
4841                /* First, remove the old entries */
4842                $queryStr = "DELETE FROM `tblDocumentApproveLog` WHERE `approveID`=".$reviewID;
4843                if (!$db->getResult($queryStr)) {
4844                    $db->rollbackTransaction();
4845                    return false;
4846                }
4847
4848                $queryStr = "DELETE FROM `tblDocumentApprovers` WHERE `approveID`=".$reviewID;
4849                if (!$db->getResult($queryStr)) {
4850                    $db->rollbackTransaction();
4851                    return false;
4852                }
4853            }
4854        }
4855
4856        /* Second, insert the new entries */
4857        foreach($reviewers as $review) {
4858            $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ".
4859                "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$review['type'] .", ".(is_object($review['required']) ? $review['required']->getID() : (int) $review['required']).")";
4860            if (!$db->getResult($queryStr)) {
4861                $db->rollbackTransaction();
4862                return false;
4863            }
4864            $reviewID = $db->getInsertID('tblDocumentApprovers', 'approveID');
4865            $reviewlog = array_reverse($review['logs']);
4866            foreach($reviewlog as $log) {
4867                if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
4868                    $db->rollbackTransaction();
4869                    return false;
4870                }
4871                $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
4872                    "VALUES ('".$reviewID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")";
4873                if (!$db->getResult($queryStr)) {
4874                    $db->rollbackTransaction();
4875                    return false;
4876                }
4877                $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
4878                if(!empty($log['file'])) {
4879                    SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID);
4880                }
4881            }
4882        }
4883
4884        $db->commitTransaction();
4885        return true;
4886    } /* }}} */
4887
4888    /**
4889     * Return a list of all recipients separated by individuals and groups
4890     * This list will not take the receipt log into account. Therefore it
4891     * can contain recipients which has actually been deleted as a recipient.
4892     *
4893     * @return array|bool|null
4894     */
4895    function getRecipients() { /* {{{ */
4896        $dms = $this->_document->getDMS();
4897        $db = $dms->getDB();
4898
4899        $queryStr=
4900            "SELECT * FROM `tblDocumentRecipients` WHERE `version`='".$this->_version
4901            ."' AND `documentID` = '". $this->_document->getID() ."' ";
4902
4903        $recs = $db->getResultArray($queryStr);
4904        if (is_bool($recs))
4905            return false;
4906        $recipients = array('i'=>array(), 'g'=>array());
4907        foreach($recs as $rec) {
4908            if($rec['type'] == 0) {
4909                if($u = $dms->getUser($rec['required']))
4910                    $recipients['i'][] = $u;
4911            } elseif($rec['type'] == 1) {
4912                if($g = $dms->getGroup($rec['required']))
4913                    $recipients['g'][] = $g;
4914            }
4915        }
4916        return $recipients;
4917    } /* }}} */
4918
4919    /**
4920     * Get the current receipt status of the document content
4921     * The receipt status is a list of receipts
4922     *
4923     * @param integer $limit maximum number of status changes per receiver
4924     * @return array list of receipts
4925     */
4926    function getReceiptStatus($limit=1) { /* {{{ */
4927        $db = $this->_document->getDMS()->getDB();
4928
4929        if (!is_numeric($limit)) return false;
4930
4931        // Retrieve the current status of each assigned reviewer for the content
4932        // represented by this object.
4933        // When just the last log entry for each recipient is needed then a single
4934        // sql statement is much faster than the code below which first retrieves
4935        // all receivers and than the logs
4936        // FIXME: caching was turned off to make list of review log in ViewDocument
4937        // possible
4938        if($limit == 1) {
4939            /* The following sql statement is somewhat optimized. The first join is
4940             * crucial because it should first take the table with the least number
4941             * of records and join the other tables. ttreceiptid join tblDocumentRecipients
4942             * is faster than tblDocumentRecipients join ttreceiptid
4943             */
4944            if (!$db->createTemporaryTable("ttreceiptid")) {
4945                return false;
4946            }
4947            $queryStr=
4948                "SELECT `tblDocumentRecipients`.*, `tblDocumentReceiptLog`.`receiptLogID`, `tblDocumentReceiptLog`.`status`, `tblDocumentReceiptLog`.`comment`, `tblDocumentReceiptLog`.`date`, `tblDocumentReceiptLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` FROM `ttreceiptid` LEFT JOIN `tblDocumentRecipients` ON `tblDocumentRecipients`.`receiptID`=`ttreceiptid`.`receiptID` LEFT JOIN `tblDocumentReceiptLog` ON `ttreceiptid`.`maxLogID`=`tblDocumentReceiptLog`.`receiptLogID` LEFT JOIN `tblUsers` ON `tblDocumentRecipients`.`required`=`tblUsers`.`id` LEFT JOIN `tblGroups` ON `tblDocumentRecipients`.`required`=`tblGroups`.`id` WHERE `version`='".$this->_version
4949                ."' AND `documentID` = '". $this->_document->getID() ."' ";
4950            $recs = $db->getResultArray($queryStr);
4951            if (is_bool($recs) && !$recs) {
4952                unset($this->_receiptStatus);
4953                return false;
4954            }
4955            $this->_receiptStatus = $recs;
4956        } elseif (1 || !isset($this->_receiptStatus)) {
4957            /* First get a list of all receipts for this document content */
4958            $queryStr=
4959                "SELECT `receiptID` FROM `tblDocumentRecipients` WHERE `version`='".$this->_version
4960                ."' AND `documentID` = '". $this->_document->getID() ."' ";
4961            $recs = $db->getResultArray($queryStr);
4962            if (is_bool($recs) && !$recs)
4963                return false;
4964            $this->_receiptStatus = array();
4965            if($recs) {
4966                foreach($recs as $rec) {
4967                    $queryStr=
4968                        "SELECT `tblDocumentRecipients`.*, `tblDocumentReceiptLog`.`receiptLogID`, ".
4969                        "`tblDocumentReceiptLog`.`status`, ".
4970                        "`tblDocumentReceiptLog`.`comment`, ".
4971                        "`tblDocumentReceiptLog`.`date`, ".
4972                        "`tblDocumentReceiptLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ".
4973                        "FROM `tblDocumentRecipients` ".
4974                        "LEFT JOIN `tblDocumentReceiptLog` USING (`receiptID`) ".
4975                        "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentRecipients`.`required` ".
4976                        "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentRecipients`.`required` ".
4977                        "WHERE `tblDocumentRecipients`.`receiptID` = '". $rec['receiptID'] ."' ".
4978                        "ORDER BY `tblDocumentReceiptLog`.`receiptLogID` DESC LIMIT ".(int) $limit;
4979
4980                    $res = $db->getResultArray($queryStr);
4981                    if (is_bool($res) && !$res) {
4982                        unset($this->_receiptStatus);
4983                        return false;
4984                    }
4985                    $this->_receiptStatus = array_merge($this->_receiptStatus, $res);
4986                }
4987            }
4988        }
4989        return $this->_receiptStatus;
4990    } /* }}} */
4991
4992    /**
4993     * Get the latest entries from the receipt log of the document content
4994     *
4995     * @param integer $limit the number of log entries returned, defaults to 1
4996     * @return array list of receiptlog entries
4997     */
4998    function getReceiptLog($limit=1) { /* {{{ */
4999        $db = $this->_document->getDMS()->getDB();
5000
5001        if (!is_numeric($limit)) return false;
5002
5003        $queryStr=
5004            "SELECT * FROM `tblDocumentReceiptLog` LEFT JOIN `tblDocumentRecipients` ON  `tblDocumentReceiptLog`.`receiptID` = `tblDocumentRecipients`.`receiptID` WHERE `version`='".$this->_version
5005            ."' AND `documentID` = '". $this->_document->getID() ."' "
5006            ."ORDER BY `tblDocumentReceiptLog`.`receiptLogID` DESC LIMIT ".(int) $limit;
5007        $recs = $db->getResultArray($queryStr);
5008        if (is_bool($recs) && !$recs)
5009            return false;
5010        return($recs);
5011    } /* }}} */
5012
5013    /**
5014     * Rewrites the complete receipt log
5015     * 
5016     * Attention: this function is highly dangerous.
5017     * It removes an existing receipt log and rewrites it.
5018     * This method was added for importing an xml dump.
5019     *
5020     * @param array $receiptlog new status log with the newest log entry first.
5021     * @return boolean true on success, otherwise false
5022     */
5023    function rewriteReceiptLog($recipients) { /* {{{ */
5024        $db = $this->_document->getDMS()->getDB();
5025
5026        $queryStr= "SELECT `tblDocumentRecipients`.* FROM `tblDocumentRecipients` WHERE `tblDocumentRecipients`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentRecipients`.`version` = '". $this->_version ."' ";
5027        $res = $db->getResultArray($queryStr);
5028        if (is_bool($res) && !$res)
5029            return false;
5030
5031        $db->startTransaction();
5032
5033        if($res) {
5034            foreach($res as $receipt) {
5035                $receiptID = $receipt['receiptID'];
5036
5037                /* First, remove the old entries */
5038                $queryStr = "DELETE from `tblDocumentReceiptLog` where `receiptID`=".$receiptID;
5039                if (!$db->getResult($queryStr)) {
5040                    $db->rollbackTransaction();
5041                    return false;
5042                }
5043
5044                $queryStr = "DELETE from `tblDocumentRecipients` where `receiptID`=".$receiptID;
5045                if (!$db->getResult($queryStr)) {
5046                    $db->rollbackTransaction();
5047                    return false;
5048                }
5049            }
5050        }
5051
5052        /* Second, insert the new entries */
5053        foreach($recipients as $receipt) {
5054            $queryStr = "INSERT INTO `tblDocumentRecipients` (`documentID`, `version`, `type`, `required`) ".
5055                "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$receipt['type'] .", ".(is_object($receipt['required']) ? $receipt['required']->getID() : (int) $receipt['required']).")";
5056            if (!$db->getResult($queryStr)) {
5057                $db->rollbackTransaction();
5058                return false;
5059            }
5060            $receiptID = $db->getInsertID('tblDocumentRecipients', 'receiptID');
5061            $receiptlog = array_reverse($receipt['logs']);
5062            foreach($receiptlog as $log) {
5063                if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
5064                    $db->rollbackTransaction();
5065                    return false;
5066                }
5067                $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`, `comment`, `date`, `userID`) ".
5068                    "VALUES ('".$receiptID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")";
5069                if (!$db->getResult($queryStr)) {
5070                    $db->rollbackTransaction();
5071                    return false;
5072                }
5073                $receiptLogID = $db->getInsertID('tblDocumentReceiptLog', 'receiptLogID');
5074                if(!empty($log['file'])) {
5075                    SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'r' . $receiptLogID);
5076                }
5077            }
5078        }
5079
5080        $db->commitTransaction();
5081        return true;
5082    } /* }}} */
5083
5084    /**
5085     * Return a list of all revisors separated by individuals and groups
5086     * This list will not take the revision log into account. Therefore it
5087     * can contain revisors which has actually been deleted as a revisor.
5088     *
5089     * @return array|bool|null
5090     */
5091    function getRevisors() { /* {{{ */
5092        $dms = $this->_document->getDMS();
5093        $db = $dms->getDB();
5094
5095        $queryStr=
5096            "SELECT * FROM `tblDocumentRevisors` WHERE `version`='".$this->_version
5097            ."' AND `documentID` = '". $this->_document->getID() ."' ";
5098
5099        $recs = $db->getResultArray($queryStr);
5100        if (is_bool($recs))
5101            return false;
5102        $revisors = array('i'=>array(), 'g'=>array());
5103        foreach($recs as $rec) {
5104            if($rec['type'] == 0) {
5105                if($u = $dms->getUser($rec['required']))
5106                    $revisors['i'][] = $u;
5107            } elseif($rec['type'] == 1) {
5108                if($g = $dms->getGroup($rec['required']))
5109                    $revisors['g'][] = $g;
5110            }
5111        }
5112        return $revisors;
5113    } /* }}} */
5114
5115    /**
5116     * Get the current revision status of the document content
5117     * The revision status is a list of revisions
5118     * If $limit is 1 it will return just the last log entry for each
5119     * revisor.
5120     * Keep in mind that a revision log may contain repeating revisions.
5121     *
5122     * @param integer $limit maximum number of records per revisor
5123     * @return array list of revisions
5124     */
5125    function getRevisionStatus($limit=1) { /* {{{ */
5126        $db = $this->_document->getDMS()->getDB();
5127
5128        if (!is_numeric($limit)) return false;
5129
5130        // Retrieve the current status of each assigned reviewer for the content
5131        // represented by this object.
5132        // FIXME: caching was turned off to make list of review log in ViewDocument
5133        // possible
5134        if (1 || !isset($this->_revisionStatus)) {
5135            /* First get a list of all revisions for this document content */
5136            $queryStr=
5137                "SELECT `revisionID` FROM `tblDocumentRevisors` WHERE `version`='".$this->_version
5138                ."' AND `documentID` = '". $this->_document->getID() ."' ";
5139            $recs = $db->getResultArray($queryStr);
5140            if (is_bool($recs) && !$recs)
5141                return false;
5142            $this->_revisionStatus = array();
5143            if($recs) {
5144                foreach($recs as $rec) {
5145                    $queryStr=
5146                        "SELECT `tblDocumentRevisors`.*, `tblDocumentRevisionLog`.`revisionLogID`, ".
5147                        "`tblDocumentRevisionLog`.`status`, ".
5148                        "`tblDocumentRevisionLog`.`comment`, ".
5149                        "`tblDocumentRevisionLog`.`date`, ".
5150                        "`tblDocumentRevisionLog`.`userID`, `tblUsers`.`fullName`, `tblGroups`.`name` AS `groupName` ".
5151                        "FROM `tblDocumentRevisors` ".
5152                        "LEFT JOIN `tblDocumentRevisionLog` USING (`revisionID`) ".
5153                        "LEFT JOIN `tblUsers` on `tblUsers`.`id` = `tblDocumentRevisors`.`required` ".
5154                        "LEFT JOIN `tblGroups` on `tblGroups`.`id` = `tblDocumentRevisors`.`required` ".
5155                        "WHERE `tblDocumentRevisors`.`revisionID` = '". $rec['revisionID'] ."' ".
5156                        "ORDER BY `tblDocumentRevisionLog`.`revisionLogID` DESC LIMIT ".(int) $limit;
5157
5158                    $res = $db->getResultArray($queryStr);
5159                    if (is_bool($res) && !$res) {
5160                        unset($this->_revisionStatus);
5161                        return false;
5162                    }
5163                    $this->_revisionStatus = array_merge($this->_revisionStatus, $res);
5164                }
5165            }
5166        }
5167        return $this->_revisionStatus;
5168    } /* }}} */
5169
5170    /**
5171     * Get the latest entries from the revision log of the document content
5172     *
5173     * @param integer $limit the number of log entries returned, defaults to 1
5174     * @return array list of revisionlog entries
5175     */
5176    function getRevisionLog($limit=1) { /* {{{ */
5177        $db = $this->_document->getDMS()->getDB();
5178
5179        if (!is_numeric($limit)) return false;
5180
5181        $queryStr=
5182            "SELECT * FROM `tblDocumentRevisionLog` LEFT JOIN `tblDocumentRevisors` ON  `tblDocumentRevisionLog`.`revisionID` = `tblDocumentRevisors`.`revisionID` WHERE `version`='".$this->_version
5183            ."' AND `documentID` = '". $this->_document->getID() ."' "
5184            ."ORDER BY `tblDocumentRevisionLog`.`revisionLogID` DESC LIMIT ".(int) $limit;
5185        $recs = $db->getResultArray($queryStr);
5186        if (is_bool($recs) && !$recs)
5187            return false;
5188        return($recs);
5189    } /* }}} */
5190
5191    /**
5192     * Rewrites the complete revision log
5193     * 
5194     * Attention: this function is highly dangerous.
5195     * It removes an existing revision log and rewrites it.
5196     * This method was added for importing an xml dump.
5197     *
5198     * @param array $revisionlog new status log with the newest log entry first.
5199     * @return boolean 0 on success, otherwise a negativ error number
5200     */
5201    function rewriteRevisionLog($revisions) { /* {{{ */
5202        $db = $this->_document->getDMS()->getDB();
5203
5204        $queryStr= "SELECT `tblDocumentRevisors`.* FROM `tblDocumentRevisors` WHERE `tblDocumentRevisors`.`documentID` = '". $this->_document->getID() ."' AND `tblDocumentRevisors`.`version` = '". $this->_version ."' ";
5205        $res = $db->getResultArray($queryStr);
5206        if (is_bool($res) && !$res)
5207            return false;
5208
5209        $db->startTransaction();
5210
5211        if($res) {
5212            foreach($res as $revision) {
5213                $revisionID = $revision['revisionID'];
5214
5215                /* First, remove the old entries */
5216                $queryStr = "DELETE from `tblDocumentRevisionLog` where `revisionID`=".$revisionID;
5217                if (!$db->getResult($queryStr)) {
5218                    $db->rollbackTransaction();
5219                    return false;
5220                }
5221
5222                $queryStr = "DELETE from `tblDocumentRevisors` where `revisionID`=".$revisionID;
5223                if (!$db->getResult($queryStr)) {
5224                    $db->rollbackTransaction();
5225                    return false;
5226                }
5227            }
5228        }
5229
5230        /* Second, insert the new entries */
5231        foreach($revisions as $revision) {
5232            $queryStr = "INSERT INTO `tblDocumentRevisors` (`documentID`, `version`, `type`, `required`) ".
5233                "VALUES ('".$this->_document->getID()."', '".$this->_version."', ".$revision['type'] .", ".(is_object($revision['required']) ? $revision['required']->getID() : (int) $revision['required']).")";
5234            if (!$db->getResult($queryStr)) {
5235                $db->rollbackTransaction();
5236                return false;
5237            }
5238            $revisionID = $db->getInsertID('tblDocumentRevisors', 'revisionID');
5239            $revisionlog = array_reverse($revision['logs']);
5240            foreach($revisionlog as $log) {
5241                if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
5242                    $db->rollbackTransaction();
5243                    return false;
5244                }
5245                $queryStr = "INSERT INTO `tblDocumentRevisionLog` (`revisionID`, `status`, `comment`, `date`, `userID`) ".
5246                    "VALUES ('".$revisionID ."', '".(int) $log['status']."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".(is_object($log['user']) ? $log['user']->getID() : (int) $log['user']).")";
5247                if (!$db->getResult($queryStr)) {
5248                    $db->rollbackTransaction();
5249                    return false;
5250                }
5251                $revisionLogID = $db->getInsertID('tblDocumentRevisionLog', 'revisionLogID');
5252                if(!empty($log['file'])) {
5253                    SeedDMS_Core_File::copyFile($log['file'], $this->_dms->contentDir . $this->_document->getDir() . 'r' . $revisionLogID);
5254                }
5255            }
5256        }
5257
5258        $db->commitTransaction();
5259        return true;
5260    } /* }}} */
5261
5262    /**
5263     * Check if document version has a scheduled revision workflow.
5264     * The method will update the document status log database table
5265     * if needed and set the revisiondate of the content to $next.
5266     *
5267     * FIXME: This method does not check if there are any revisors left. Even
5268     * if all revisors have been removed, it will still start the revision workflow!
5269     * NOTE: This seems not the case anymore. The status of each revision is
5270     * checked. Only if at least one status is S_LOG_SLEEPING the revision will be
5271     * started. This wouldn't be the case if all revisors had been removed.
5272     *
5273     * @param object $user user requesting the possible automatic change
5274     * @param string $next next date for review
5275     * @return boolean true if status has changed
5276     */
5277    function checkForDueRevisionWorkflow($user, $next=''){ /* {{{ */
5278        $st=$this->getStatus();
5279
5280        /* A revision workflow will only be started if the document version is released */
5281        if($st["status"] == S_RELEASED) {
5282            /* First check if there are any scheduled revisions currently sleeping */
5283            $pendingRevision=false;
5284            unset($this->_revisionStatus);  // force to be reloaded from DB
5285            $revisionStatus=$this->getRevisionStatus();
5286            if (is_array($revisionStatus) && count($revisionStatus)>0) {
5287                foreach ($revisionStatus as $a){
5288                    if ($a["status"]==S_LOG_SLEEPING || $a["status"]==S_LOG_SLEEPING){
5289                        $pendingRevision=true;
5290                        break;
5291                    }
5292                }
5293            }
5294            if(!$pendingRevision)
5295                return false;
5296
5297            /* We have sleeping revision, next check if the revision is already due */
5298            if($this->getRevisionDate() && $this->getRevisionDate() <= date('Y-m-d 00:00:00')) {
5299                if($this->startRevision($user, 'Automatic start of revision workflow scheduled for '.$this->getRevisionDate())) {
5300                    if($next) {
5301                        $tmp = explode('-', substr($next, 0, 10));
5302                        if(checkdate($tmp[1], $tmp[2], $tmp[0]))
5303                            $this->setRevisionDate($next);
5304                    } else {
5305                        $this->setRevisionDate(false);
5306                    }
5307                    return true;
5308                }
5309            }
5310        }
5311        return false;
5312    } /* }}} */
5313
5314    /**
5315     * Add user as new reviewer
5316     *
5317     * @param object $user user in charge for the review
5318     * @param object $requestUser user requesting the operation (usually the
5319     * currently logged in user)
5320     *
5321     * @return integer|false if > 0 the id of the review log, if < 0 the error
5322     * code, false in case of an sql error
5323     */
5324    public function addIndReviewer($user, $requestUser) { /* {{{ */
5325        if(!$user || !$requestUser)
5326            return -1;
5327
5328        $db = $this->_document->getDMS()->getDB();
5329
5330        if(!$user->isType('user'))
5331            return -1;
5332
5333        $userID = $user->getID();
5334
5335        // Get the list of users and groups with read access to this document.
5336        if($this->_document->getAccessMode($user) < M_READ) {
5337            return -2;
5338        }
5339
5340        // Check to see if the user has already been added to the review list.
5341        $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version);
5342        if (is_bool($reviewStatus) && !$reviewStatus) {
5343            return false;
5344        }
5345        $indstatus = false;
5346        if (count($reviewStatus["indstatus"]) > 0) {
5347            $indstatus = array_pop($reviewStatus["indstatus"]);
5348            if($indstatus["status"]!=-2) {
5349                // User is already on the list of reviewers; return an error.
5350                return -3;
5351            }
5352        }
5353
5354        // Add the user into the review database.
5355        if (!$indstatus || ($indstatus && $indstatus["status"]!=-2)) {
5356            $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ".
5357                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '0', '". $userID ."')";
5358            $res = $db->getResult($queryStr);
5359            if (is_bool($res) && !$res) {
5360                return false;
5361            }
5362            $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID');
5363        }
5364        else {
5365            $reviewID = isset($indstatus["reviewID"]) ? $indstatus["reviewID"] : NULL;
5366        }
5367
5368        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
5369            "VALUES ('". $reviewID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5370        $res = $db->getResult($queryStr);
5371        if (is_bool($res) && !$res) {
5372            return false;
5373        }
5374
5375        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
5376        $db->dropTemporaryTable('ttreviewid');
5377        return $reviewLogID;
5378    } /* }}} */
5379
5380    /**
5381     * Add group as new reviewer
5382     *
5383     * @param object $group group in charge for the review
5384     * @param object $requestUser user requesting the operation (usually the
5385     * currently logged in user)
5386     *
5387     * @return integer|false if > 0 the id of the review log, if < 0 the error
5388     * code, false in case of an sql error
5389     */
5390    public function addGrpReviewer($group, $requestUser) { /* {{{ */
5391        if(!$group || !$requestUser)
5392            return -1;
5393
5394        $db = $this->_document->getDMS()->getDB();
5395
5396        if(!$group->isType('group'))
5397            return -1;
5398
5399        $groupID = $group->getID();
5400
5401        // Get the list of users and groups with read access to this document.
5402        if (!isset($this->_readAccessList)) {
5403            // TODO: error checking.
5404            $this->_readAccessList = $this->_document->getReadAccessList();
5405        }
5406        $approved = false;
5407        foreach ($this->_readAccessList["groups"] as $appGroup) {
5408            if ($groupID == $appGroup->getID()) {
5409                $approved = true;
5410                break;
5411            }
5412        }
5413        if (!$approved) {
5414            return -2;
5415        }
5416
5417        // Check to see if the group has already been added to the review list.
5418        $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version);
5419        if (is_bool($reviewStatus) && !$reviewStatus) {
5420            return false;
5421        }
5422        if (count($reviewStatus) > 0 && $reviewStatus[0]["status"]!=-2) {
5423            // Group is already on the list of reviewers; return an error.
5424            return -3;
5425        }
5426
5427        // Add the group into the review database.
5428        if (!isset($reviewStatus[0]["status"]) || (isset($reviewStatus[0]["status"]) && $reviewStatus[0]["status"]!=-2)) {
5429            $queryStr = "INSERT INTO `tblDocumentReviewers` (`documentID`, `version`, `type`, `required`) ".
5430                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '1', '". $groupID ."')";
5431            $res = $db->getResult($queryStr);
5432            if (is_bool($res) && !$res) {
5433                return false;
5434            }
5435            $reviewID = $db->getInsertID('tblDocumentReviewers', 'reviewID');
5436        }
5437        else {
5438            $reviewID = isset($reviewStatus[0]["reviewID"])?$reviewStatus[0]["reviewID"]:NULL;
5439        }
5440
5441        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
5442            "VALUES ('". $reviewID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5443        $res = $db->getResult($queryStr);
5444        if (is_bool($res) && !$res) {
5445            return false;
5446        }
5447
5448        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
5449        $db->dropTemporaryTable('ttreviewid');
5450        return $reviewLogID;
5451    } /* }}} */
5452
5453    /**
5454     * Add a review to the document content
5455     *
5456     * This method will add an entry to the table tblDocumentReviewLog.
5457     * It will first check if the user is ment to review the document version.
5458     * It not the return value is -3.
5459     * Next it will check if the users has been removed from the list of
5460     * reviewers. In that case -4 will be returned.
5461     * If the given review status has been set by the user before, it cannot
5462     * be set again and 0 will be returned. Ð†f the review could be succesfully
5463     * added, the review log id will be returned.
5464     *
5465     * @see SeedDMS_Core_DocumentContent::setApprovalByInd()
5466     *
5467     * @param object  $user user doing the review
5468     * @param object  $requestUser user asking for the review, this is mostly
5469     * the user currently logged in.
5470     * @param integer $status status of review
5471     * @param string  $comment comment for review
5472     *
5473     * @return integer|bool new review log id, error code 0 till -4,
5474     * false in case of an sql error
5475     */
5476    public function setReviewByInd($user, $requestUser, $status, $comment, $file='') { /* {{{ */
5477        if(!$user || !$requestUser)
5478            return -1;
5479
5480        $db = $this->_document->getDMS()->getDB();
5481
5482        if(!$user->isType('user'))
5483            return -1;
5484
5485        // Check if the user is on the review list at all.
5486        $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version);
5487        if (is_bool($reviewStatus) && !$reviewStatus) {
5488            return false;
5489        }
5490        if (count($reviewStatus["indstatus"])==0) {
5491            // User is not assigned to review this document. No action required.
5492            // Return an error.
5493            return -3;
5494        }
5495        $indstatus = array_pop($reviewStatus["indstatus"]);
5496        if ($indstatus["status"]==-2) {
5497            // User has been deleted from reviewers
5498            return -4;
5499        }
5500        // Check if the status is really different from the current status
5501        if ($indstatus["status"] == $status)
5502            return 0;
5503
5504        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`,
5505            `comment`, `date`, `userID`) ".
5506            "VALUES ('". $indstatus["reviewID"] ."', '".
5507            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
5508            $requestUser->getID() ."')";
5509        $res=$db->getResult($queryStr);
5510        if (is_bool($res) && !$res)
5511            return false;
5512
5513        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
5514        if($file) {
5515            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID);
5516        }
5517        return $reviewLogID;
5518    } /* }}} */
5519
5520    /**
5521     * Add another entry to review log which resets the status
5522     *
5523     * This method will not delete anything from the database, but will add
5524     * a new review log entry which sets the status to 0. This is only allowed
5525     * if the current status is either 1 (reviewed) or -1 (rejected).
5526     *
5527     * After calling this method SeedDMS_Core_DocumentContent::verifyStatus()
5528     * should be called to recalculate the document status.
5529     *
5530     * @param integer $reviewid id of review
5531     * @param SeedDMS_Core_User $requestUser user requesting the removal
5532     * @param string $comment comment
5533     *
5534     * @return integer|bool true if successful, error code < 0,
5535     * false in case of an sql error
5536     */
5537    public function removeReview($reviewid, $requestUser, $comment='') { /* {{{ */
5538        $db = $this->_document->getDMS()->getDB();
5539
5540        // Check to see if the user can be removed from the review list.
5541        $reviews = $this->getReviewStatus();
5542        if (is_bool($reviews) && !$reviews) {
5543            return false;
5544        }
5545        $reviewStatus = null;
5546        foreach($reviews as $review) {
5547            if($review['reviewID'] == $reviewid) {
5548                $reviewStatus = $review;
5549                break;
5550            }
5551        }
5552        if(!$reviewStatus)
5553            return -2;
5554
5555        // The review log entry may only be removed if the status is 1 or -1
5556        if ($reviewStatus["status"] != 1 && $reviewStatus["status"] != -1)
5557            return -3;
5558
5559        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`,
5560            `comment`, `date`, `userID`) ".
5561            "VALUES ('". $reviewStatus["reviewID"] ."', '0', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
5562            $requestUser->getID() ."')";
5563        $res=$db->getResult($queryStr);
5564        if (is_bool($res) && !$res)
5565            return false;
5566
5567        return true;
5568    } /* }}} */
5569
5570    /**
5571     * Add a review to the document content
5572     *
5573     * This method is similar to
5574     * {@see SeedDMS_Core_DocumentContent::setReviewByInd()} but adds a review
5575     * for a group instead of a user.
5576     *
5577     * @param object  $group group doing the review
5578     * @param object  $requestUser user asking for the review, this is mostly
5579     * the user currently logged in.
5580     * @param integer $status status of review
5581     * @param string  $comment comment for review
5582     *
5583     * @return integer|bool new review log id, error code 0 till -4,
5584     * false in case of an sql error
5585     */
5586    public function setReviewByGrp($group, $requestUser, $status, $comment, $file='') { /* {{{ */
5587        if(!$group || !$requestUser)
5588            return -1;
5589
5590        $db = $this->_document->getDMS()->getDB();
5591
5592        if(!$group->isType('group'))
5593            return -1;
5594
5595        // Check if the group is on the review list at all.
5596        $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version);
5597        if (is_bool($reviewStatus) && !$reviewStatus) {
5598            return false;
5599        }
5600        if (count($reviewStatus)==0) {
5601            // User is not assigned to review this document. No action required.
5602            // Return an error.
5603            return -3;
5604        }
5605        if ((int) $reviewStatus[0]["status"]==-2) {
5606            // Group has been deleted from reviewers
5607            return -4;
5608        }
5609
5610        // Check if the status is really different from the current status
5611        if ($reviewStatus[0]["status"] == $status)
5612            return 0;
5613
5614        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`,
5615            `comment`, `date`, `userID`) ".
5616            "VALUES ('". $reviewStatus[0]["reviewID"] ."', '".
5617            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
5618            $requestUser->getID() ."')";
5619        $res=$db->getResult($queryStr);
5620        if (is_bool($res) && !$res)
5621            return false;
5622
5623        $reviewLogID = $db->getInsertID('tblDocumentReviewLog', 'reviewLogID');
5624        if($file) {
5625            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'r' . $reviewLogID);
5626        }
5627        return $reviewLogID;
5628 } /* }}} */
5629
5630    /**
5631     * Add user as new approver
5632     *
5633     * @param object $user user in charge for the approval
5634     * @param object $requestUser user requesting the operation (usually the
5635     * currently logged in user)
5636     *
5637     * @return integer|false if > 0 the id of the approval log, if < 0 the error
5638     * code, false in case of an sql error
5639     */
5640    public function addIndApprover($user, $requestUser) { /* {{{ */
5641        if(!$user || !$requestUser)
5642            return -1;
5643
5644        $db = $this->_document->getDMS()->getDB();
5645
5646        if(!$user->isType('user'))
5647            return -1;
5648
5649        $userID = $user->getID();
5650
5651        // Get the list of users and groups with read access to this document.
5652        if($this->_document->getAccessMode($user) < M_READ) {
5653            return -2;
5654        }
5655
5656        // Check if the user has already been added to the approvers list.
5657        $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version);
5658        if (is_bool($approvalStatus) && !$approvalStatus) {
5659            return false;
5660        }
5661        $indstatus = false;
5662        if (count($approvalStatus["indstatus"]) > 0) {
5663            $indstatus = array_pop($approvalStatus["indstatus"]);
5664            if($indstatus["status"]!=-2) {
5665                // User is already on the list of approverss; return an error.
5666                return -3;
5667            }
5668        }
5669
5670        if ( !$indstatus || (isset($indstatus["status"]) && $indstatus["status"]!=-2)) {
5671            // Add the user into the approvers database.
5672            $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ".
5673                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '0', '". $userID ."')";
5674            $res = $db->getResult($queryStr);
5675            if (is_bool($res) && !$res) {
5676                return false;
5677            }
5678            $approveID = $db->getInsertID('tblDocumentApprovers', 'approveID');
5679        }
5680        else {
5681            $approveID = isset($indstatus["approveID"]) ? $indstatus["approveID"] : NULL;
5682        }
5683
5684        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
5685            "VALUES ('". $approveID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5686        $res = $db->getResult($queryStr);
5687        if (is_bool($res) && !$res) {
5688            return false;
5689        }
5690
5691        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
5692        $db->dropTemporaryTable('ttapproveid');
5693        return $approveLogID;
5694    } /* }}} */
5695
5696    /**
5697     * Add group as new approver
5698     *
5699     * @param object $group group in charge for the approval
5700     * @param object $requestUser user requesting the operation (usually the
5701     * currently logged in user)
5702     *
5703     * @return integer|false if > 0 the id of the approval log, if < 0 the error
5704     * code, false in case of an sql error
5705     */
5706    public function addGrpApprover($group, $requestUser) { /* {{{ */
5707        if(!$group || !$requestUser)
5708            return -1;
5709
5710        $db = $this->_document->getDMS()->getDB();
5711
5712        if(!$group->isType('group'))
5713            return -1;
5714
5715        $groupID = $group->getID();
5716
5717        // Get the list of users and groups with read access to this document.
5718        if (!isset($this->_readAccessList)) {
5719            // TODO: error checking.
5720            $this->_readAccessList = $this->_document->getReadAccessList();
5721        }
5722        $approved = false;
5723        foreach ($this->_readAccessList["groups"] as $appGroup) {
5724            if ($groupID == $appGroup->getID()) {
5725                $approved = true;
5726                break;
5727            }
5728        }
5729        if (!$approved) {
5730            return -2;
5731        }
5732
5733        // Check if the group has already been added to the approver list.
5734        $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version);
5735        if (is_bool($approvalStatus) && !$approvalStatus) {
5736            return false;
5737        }
5738        if (count($approvalStatus) > 0 && $approvalStatus[0]["status"]!=-2) {
5739            // Group is already on the list of approvers; return an error.
5740            return -3;
5741        }
5742
5743        // Add the group into the approver database.
5744        if (!isset($approvalStatus[0]["status"]) || (isset($approvalStatus[0]["status"]) && $approvalStatus[0]["status"]!=-2)) {
5745            $queryStr = "INSERT INTO `tblDocumentApprovers` (`documentID`, `version`, `type`, `required`) ".
5746                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '1', '". $groupID ."')";
5747            $res = $db->getResult($queryStr);
5748            if (is_bool($res) && !$res) {
5749                return false;
5750            }
5751            $approveID = $db->getInsertID('tblDocumentApprovers', 'approveID');
5752        }
5753        else {
5754            $approveID = isset($approvalStatus[0]["approveID"])?$approvalStatus[0]["approveID"]:NULL;
5755        }
5756
5757        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
5758            "VALUES ('". $approveID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5759        $res = $db->getResult($queryStr);
5760        if (is_bool($res) && !$res) {
5761            return false;
5762        }
5763
5764        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
5765        $db->dropTemporaryTable('ttapproveid');
5766        return $approveLogID;
5767    } /* }}} */
5768
5769    /**
5770     * Sets approval status of a document content for a user
5771     *
5772     * This method can be used to approve or reject a document content, or
5773     * to reset its approval state. In most cases this function will be
5774     * called by an user, but  an admin may set the approval for
5775     * somebody else.
5776     * It is first checked if the user is in the list of approvers at all.
5777     * Then it is check if the approval status is already -2. In both cases
5778     * the function returns with an error.
5779     *
5780     * @see SeedDMS_Core_DocumentContent::setReviewByInd()
5781     *
5782     * @param object  $user user in charge for doing the approval
5783     * @param object  $requestUser user actually calling this function
5784     * @param integer $status the status of the approval, possible values are
5785     *        0=unprocessed (maybe used to reset a status)
5786     *        1=approved,
5787     *       -1=rejected,
5788     *       -2=user is deleted (use {link
5789     *       SeedDMS_Core_DocumentContent::delIndApprover} instead)
5790     * @param string $comment approval comment
5791     *
5792     * @return integer|bool new review log id, error code 0 till -4,
5793     * false in case of an sql error
5794     */
5795    public function setApprovalByInd($user, $requestUser, $status, $comment, $file='') { /* {{{ */
5796        if(!$user || !$requestUser)
5797            return -1;
5798
5799        $db = $this->_document->getDMS()->getDB();
5800
5801        if(!$user->isType('user'))
5802            return -1;
5803
5804        // Check if the user is on the approval list at all.
5805        $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version);
5806        if (is_bool($approvalStatus) && !$approvalStatus) {
5807            return false;
5808        }
5809        if (count($approvalStatus["indstatus"])==0) {
5810            // User is not assigned to approve this document. No action required.
5811            // Return an error.
5812            return -3;
5813        }
5814        $indstatus = array_pop($approvalStatus["indstatus"]);
5815        if ($indstatus["status"]==-2) {
5816            // User has been deleted from approvers
5817            return -4;
5818        }
5819        // Check if the status is really different from the current status
5820        if ($indstatus["status"] == $status)
5821            return 0;
5822
5823        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`,
5824            `comment`, `date`, `userID`) ".
5825            "VALUES ('". $indstatus["approveID"] ."', '".
5826            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
5827            $requestUser->getID() ."')";
5828        $res=$db->getResult($queryStr);
5829        if (is_bool($res) && !$res)
5830            return false;
5831
5832        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
5833        if($file) {
5834            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID);
5835        }
5836        return $approveLogID;
5837    } /* }}} */
5838
5839    /**
5840     * Add another entry to approval log which resets the status
5841     *
5842     * This method will not delete anything from the database, but will add
5843     * a new approval log entry which sets the status to 0. This is only allowed
5844     * if the current status is either 1 (approved) or -1 (rejected).
5845     *
5846     * After calling this method SeedDMS_Core_DocumentContent::verifyStatus()
5847     * should be called to recalculate the document status.
5848     *
5849     * @param integer $approveid id of approval
5850     * @param SeedDMS_Core_User $requestUser user requesting the removal
5851     * @param string $comment comment
5852     *
5853     * @return integer|bool true if successful, error code < 0,
5854     * false in case of an sql error
5855     */
5856    public function removeApproval($approveid, $requestUser, $comment='') { /* {{{ */
5857        $db = $this->_document->getDMS()->getDB();
5858
5859        // Check to see if the user can be removed from the approval list.
5860        $approvals = $this->getApprovalStatus();
5861        if (is_bool($approvals) && !$approvals) {
5862            return false;
5863        }
5864        $approvalStatus = null;
5865        foreach($approvals as $approval) {
5866            if($approval['approveID'] == $approveid) {
5867                $approvalStatus = $approval;
5868                break;
5869            }
5870        }
5871        if(!$approvalStatus)
5872            return -2;
5873
5874        // The approval log entry may only be removed if the status is 1 or -1
5875        if ($approvalStatus["status"] != 1 && $approvalStatus["status"] != -1)
5876            return -3;
5877
5878        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`,
5879            `comment`, `date`, `userID`) ".
5880            "VALUES ('". $approvalStatus["approveID"] ."', '0', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
5881            $requestUser->getID() ."')";
5882        $res=$db->getResult($queryStr);
5883        if (is_bool($res) && !$res)
5884            return false;
5885
5886        return true;
5887 } /* }}} */
5888
5889    /**
5890     * Sets approval status of a document content for a group
5891     *
5892     * The functions behaves like
5893     * {link SeedDMS_Core_DocumentContent::setApprovalByInd} but does it for
5894     * a group instead of a user
5895     */
5896    public function setApprovalByGrp($group, $requestUser, $status, $comment, $file='') { /* {{{ */
5897        if(!$group || !$requestUser)
5898            return -1;
5899
5900        $db = $this->_document->getDMS()->getDB();
5901
5902        if(!$group->isType('group'))
5903            return -1;
5904
5905        // Check if the group is on the approval list at all.
5906        $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version);
5907        if (is_bool($approvalStatus) && !$approvalStatus) {
5908            return false;
5909        }
5910        if (count($approvalStatus)==0) {
5911            // User is not assigned to approve this document. No action required.
5912            // Return an error.
5913            return -3;
5914        }
5915        if ($approvalStatus[0]["status"]==-2) {
5916            // Group has been deleted from approvers
5917            return -4;
5918        }
5919
5920        // Check if the status is really different from the current status
5921        if ($approvalStatus[0]["status"] == $status)
5922            return 0;
5923
5924        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`,
5925            `comment`, `date`, `userID`) ".
5926            "VALUES ('". $approvalStatus[0]["approveID"] ."', '".
5927            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
5928            $requestUser->getID() ."')";
5929        $res=$db->getResult($queryStr);
5930        if (is_bool($res) && !$res)
5931            return false;
5932
5933        $approveLogID = $db->getInsertID('tblDocumentApproveLog', 'approveLogID');
5934        if($file) {
5935            SeedDMS_Core_File::copyFile($file, $this->_dms->contentDir . $this->_document->getDir() . 'a' . $approveLogID);
5936        }
5937        return $approveLogID;
5938 } /* }}} */
5939
5940    function addIndRecipient($user, $requestUser) { /* {{{ */
5941        $db = $this->_document->getDMS()->getDB();
5942
5943        if(!$user || !$requestUser)
5944            return -1;
5945
5946        if(!$user->isType('user'))
5947            return -1;
5948
5949        $userID = $user->getID();
5950
5951        // Get the list of users and groups with read access to this document.
5952        if($this->_document->getAccessMode($user) < M_READ) {
5953            return -2;
5954        }
5955
5956        // Check to see if the user has already been added to the receipt list.
5957        $receiptStatus = $user->getReceiptStatus($this->_document->getID(), $this->_version);
5958        if (is_bool($receiptStatus) && !$receiptStatus) {
5959            return -1;
5960        }
5961        $indstatus = false;
5962        if (count($receiptStatus["indstatus"]) > 0) {
5963            $indstatus = array_pop($receiptStatus["indstatus"]);
5964            if($indstatus["status"]!=-2) {
5965                // User is already on the list of recipients; return an error.
5966                return -3;
5967            }
5968        }
5969
5970        // Add the user into the recipients database.
5971        if (!$indstatus || ($indstatus && $indstatus["status"]!=-2)) {
5972            $queryStr = "INSERT INTO `tblDocumentRecipients` (`documentID`, `version`, `type`, `required`) ".
5973                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '0', '". $userID ."')";
5974            $res = $db->getResult($queryStr);
5975            if (is_bool($res) && !$res) {
5976                return -1;
5977            }
5978            $receiptID = $db->getInsertID('tblDocumentRecipients', 'receiptID');
5979        }
5980        else {
5981            $receiptID = isset($indstatus["receiptID"]) ? $indstatus["receiptID"] : NULL;
5982        }
5983
5984        $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`, `comment`, `date`, `userID`) ".
5985            "VALUES ('". $receiptID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
5986        $res = $db->getResult($queryStr);
5987        if (is_bool($res) && !$res) {
5988            return -1;
5989        }
5990
5991        // Add recipient to event notification table.
5992        //$this->_document->addNotify($userID, true);
5993
5994        $receiptLogID = $db->getInsertID('tblDocumentReceiptLog', 'receiptLogID');
5995        $db->dropTemporaryTable('ttreceiptid');
5996        return $receiptLogID;
5997    } /* }}} */
5998
5999    function addGrpRecipient($group, $requestUser) { /* {{{ */
6000        $db = $this->_document->getDMS()->getDB();
6001
6002        if(!$group || !$requestUser)
6003            return -1;
6004
6005        if(!$group->isType('group'))
6006            return -1;
6007
6008        $groupID = $group->getID();
6009
6010        // Get the list of users and groups with read access to this document.
6011        if (!isset($this->_readAccessList)) {
6012            // TODO: error checking.
6013            $this->_readAccessList = $this->_document->getReadAccessList();
6014        }
6015        $approved = false;
6016        foreach ($this->_readAccessList["groups"] as $appGroup) {
6017            if ($groupID == $appGroup->getID()) {
6018                $approved = true;
6019                break;
6020            }
6021        }
6022        if (!$approved) {
6023            return -2;
6024        }
6025
6026        // Check to see if the group has already been added to the review list.
6027        $receiptStatus = $group->getReceiptStatus($this->_document->getID(), $this->_version);
6028        if (is_bool($receiptStatus) && !$receiptStatus) {
6029            return -1;
6030        }
6031        $status = false;
6032        if (count($receiptStatus) > 0) {
6033            $status = array_pop($receiptStatus);
6034            if($status["status"]!=-2) {
6035                // User is already on the list of recipients; return an error.
6036                return -3;
6037            }
6038        }
6039
6040        // Add the group into the recipients database.
6041        if (!$status || ($status && $status["status"]!=-2)) {
6042            $queryStr = "INSERT INTO `tblDocumentRecipients` (`documentID`, `version`, `type`, `required`) ".
6043                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '1', '". $groupID ."')";
6044            $res = $db->getResult($queryStr);
6045            if (is_bool($res) && !$res) {
6046                return -1;
6047            }
6048            $receiptID = $db->getInsertID('tblDocumentRecipients', 'receiptID');
6049        }
6050        else {
6051            $receiptID = isset($status["receiptID"]) ? $status["receiptID"] : NULL;
6052        }
6053
6054        $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`, `comment`, `date`, `userID`) ".
6055            "VALUES ('". $receiptID ."', '0', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6056        $res = $db->getResult($queryStr);
6057        if (is_bool($res) && !$res) {
6058            return -1;
6059        }
6060
6061        $receiptLogID = $db->getInsertID('tblDocumentReceiptLog', 'receiptLogID');
6062        $db->dropTemporaryTable('ttreceiptid');
6063        return $receiptLogID;
6064    } /* }}} */
6065
6066    /**
6067     * Add an individual revisor to the document content
6068     *
6069     * This function adds a user as a revisor but doesn't start the
6070     * revision workflow by default. This behaviour is different from all
6071     * other workflows (approval, review, receipt), because it adds
6072     * an initial entry in the revision log, which marks the revision as
6073     * 'sleeping'. The workflow is started at a later point in time by adding 
6074     * the second entry in the revision log which puts it into 'waiting'.
6075     *
6076     * @param object $user user to be added as a revisor
6077     * @param object $requestUser user requesting the addition
6078     * @return integer 0 if successful otherwise a value < 0
6079     */
6080    function addRevisor($object, $requestUser) { /* {{{ */
6081        $dms = $this->_document->getDMS();
6082        $db = $dms->getDB();
6083
6084        if(!$object || !$requestUser)
6085            return -1;
6086
6087        // Check to see if the user has already been added to the revisor list.
6088        $revisionStatus = $object->getRevisionStatus($this->_document->getID(), $this->_version);
6089        if (is_bool($revisionStatus) && !$revisionStatus) {
6090            return -1;
6091        }
6092
6093        /* getRevisionStatus() returns an array with either an element
6094         * 'indstatus' (user) or no element (group) containing the revision log
6095         */
6096        if(get_class($object) == $dms->getClassname('user')) {
6097            $revisionStatus = $revisionStatus['indstatus'];
6098            $type = 0;
6099
6100            // Get the list of users and groups with read access to this document.
6101            if($this->_document->getAccessMode($object) < M_READ) {
6102                return -2;
6103            }
6104        } elseif(get_class($object) == $dms->getClassname('group')) {
6105            $type = 1;
6106
6107            // Get the list of users and groups with read access to this document.
6108            if($this->_document->getGroupAccessMode($object) < M_READ) {
6109                return -2;
6110            }
6111        } else {
6112            return -1;
6113        }
6114
6115        /* There are two cases: 1. the user has not been added at all or 2.
6116         * the user was added before but has been removed later. In both
6117         * cases the user may be added. In case 2. 'indstatus' will be set
6118         * and the last status is -2. If it is not -2, then the user is still
6119         * in the process and cannot be added again.
6120         */
6121        $indstatus = false;
6122        if($revisionStatus) {
6123            if (count($revisionStatus) > 0) {
6124                $indstatus = array_pop($revisionStatus);
6125                if($indstatus["status"] != S_LOG_USER_REMOVED) {
6126                    // User is already on the list of recipients; return an error.
6127                    return -3;
6128                }
6129            }
6130        }
6131
6132        // Add the user into the revisors database.
6133        if (!$indstatus) {
6134            $queryStr = "INSERT INTO `tblDocumentRevisors` (`documentID`, `version`, `type`, `required`) ".
6135                "VALUES ('". $this->_document->getID() ."', '". $this->_version ."', '". $type ."', '". $object->getID() ."')";
6136            $res = $db->getResult($queryStr);
6137            if (is_bool($res) && !$res) {
6138                return -1;
6139            }
6140            $revisionID = $db->getInsertID('tblDocumentRevisors', 'revisionID');
6141        } else {
6142            $revisionID = isset($indstatus["revisionID"]) ? $indstatus["revisionID"] : NULL;
6143        }
6144
6145        /* If a user is added when the revision has already been startet, then
6146         * put it into S_LOG_WAITING otherwise into S_LOG_SLEEPING. Attention, if a
6147         * document content is in any other status but S_IN_REVISION, then it will
6148         * end up in S_LOG_SLEEPING. As this method is also called by removeFromProcesses()
6149         * when another user takes over the processes, it may happen that revisions
6150         * of document contents in status e.g. S_OBSOLETE, S_EXPIRED will change its
6151         * status from S_LOG_WAITING to S_LOG_SLEEPING. 
6152         * This could only be fixed if this method could set an initial revision status
6153         * by possibly passing it as another parameter to the method.
6154         */
6155        $st=$this->getStatus();
6156        $queryStr = "INSERT INTO `tblDocumentRevisionLog` (`revisionID`, `status`, `comment`, `date`, `userID`) ".
6157            "VALUES ('". $revisionID ."', '".($st["status"] == S_IN_REVISION ? S_LOG_WAITING : S_LOG_SLEEPING)."', '', ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6158        $res = $db->getResult($queryStr);
6159        if (is_bool($res) && !$res) {
6160            return -1;
6161        }
6162
6163        $revisionLogID = $db->getInsertID('tblDocumentRevisionLog', 'revisionLogID');
6164        $db->dropTemporaryTable('ttrevisionid');
6165        return $revisionLogID;
6166    } /* }}} */
6167
6168    function addIndRevisor($user, $requestUser, $donotstart=true) { /* {{{ */
6169        if($user && !$user->isType('user'))
6170            return -1;
6171
6172        return self::addRevisor($user, $requestUser, $donotstart);
6173    } /* }}} */
6174
6175    function addGrpRevisor($group, $requestUser, $donotstart=true) { /* {{{ */
6176        if($group && !$group->isType('group'))
6177            return -1;
6178
6179        return self::addRevisor($group, $requestUser, $donotstart);
6180    } /* }}} */
6181
6182    /**
6183     * Add a receipt to the document content
6184     *
6185     * This method will add an entry to the table tblDocumentReceiptLog.
6186     * It will first check if the user is ment to receipt the document version.
6187     * If not the return value is -3.
6188     * Next it will check if the user has been removed from the list of
6189     * recipients. In that case -4 will be returned.
6190     * If the given receipt has been set by the user before, it cannot
6191     * be set again and 0 will be returned. Ð†f the receipt could be succesfully
6192     * added, the receiptview log id will be returned.
6193     *
6194     * @see SeedDMS_Core_DocumentContent::setApprovalByInd()
6195     * @param object $user user doing the receipt
6196     * @param object $requestUser user asking for the receipt, this is mostly
6197     * @param integer $status the status of the receipt, possible values are
6198     *        0=unprocessed (may be used to reset a status)
6199     *        1=received,
6200     *       -1=rejected,
6201     *       -2=user is deleted (use {link
6202     *       SeedDMS_Core_DocumentContent::delIndRecipient} instead)
6203     * the user currently logged in.
6204     * @return integer new receipt log id
6205     */
6206    function setReceiptByInd($user, $requestUser, $status, $comment) { /* {{{ */
6207        $db = $this->_document->getDMS()->getDB();
6208
6209        if(!$user || !$requestUser)
6210            return -1;
6211
6212        if(!$user->isType('user'))
6213            return -1;
6214
6215        // Check to see if the user can be removed from the review list.
6216        $receiptStatus = $user->getReceiptStatus($this->_document->getID(), $this->_version);
6217        if (is_bool($receiptStatus) && !$receiptStatus) {
6218            return -1;
6219        }
6220        if (count($receiptStatus["indstatus"])==0) {
6221            // User is not assigned to receipt this document. No action required.
6222            // Return an error.
6223            return -3;
6224        }
6225        $indstatus = array_pop($receiptStatus["indstatus"]);
6226        if ($indstatus["status"] == S_LOG_USER_REMOVED) {
6227            // User has been deleted from recipients
6228            return -4;
6229        }
6230        // Check if the status is really different from the current status
6231        if ($indstatus["status"] == $status)
6232            return 0;
6233
6234        $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`,
6235            `comment`, `date`, `userID`) ".
6236            "VALUES ('". $indstatus["receiptID"] ."', '".
6237            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
6238            $requestUser->getID() ."')";
6239        $res=$db->getResult($queryStr);
6240        if (is_bool($res) && !$res)
6241            return -1;
6242        else {
6243            $receiptLogID = $db->getInsertID('tblDocumentReceiptLog', 'receiptLogID');
6244            return $receiptLogID;
6245        }
6246 } /* }}} */
6247
6248    /**
6249     * Add a receipt to the document content
6250     *
6251     * This method is similar to
6252     * {@see SeedDMS_Core_DocumentContent::setReceiptByInd()} but adds a receipt
6253     * for a group instead of a user.
6254     *
6255     * @param object $group group doing the receipt
6256     * @param object $requestUser user asking for the receipt, this is mostly
6257     * the user currently logged in.
6258     * @return integer new receipt log id
6259     */
6260    function setReceiptByGrp($group, $requestUser, $status, $comment) { /* {{{ */
6261        $db = $this->_document->getDMS()->getDB();
6262
6263        if(!$group || !$requestUser)
6264            return -1;
6265
6266        if(!$group->isType('group'))
6267            return -1;
6268
6269        // Check to see if the user can be removed from the recipient list.
6270        $receiptStatus = $group->getReceiptStatus($this->_document->getID(), $this->_version);
6271        if (is_bool($receiptStatus) && !$receiptStatus) {
6272            return -1;
6273        }
6274        if (count($receiptStatus)==0) {
6275            // User is not assigned to receipt this document. No action required.
6276            // Return an error.
6277            return -3;
6278        }
6279        $grpstatus = array_pop($receiptStatus);
6280        if ($grpstatus["status"] == S_LOG_USER_REMOVED) {
6281            // Group has been deleted from recipients
6282            return -4;
6283        }
6284
6285        // Check if the status is really different from the current status
6286        if ($grpstatus["status"] == $status)
6287            return 0;
6288
6289        $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`,
6290            `comment`, `date`, `userID`) ".
6291            "VALUES ('". $grpstatus["receiptID"] ."', '".
6292            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
6293            $requestUser->getID() ."')";
6294        $res=$db->getResult($queryStr);
6295        if (is_bool($res) && !$res)
6296            return -1;
6297        else {
6298            $receiptLogID = $db->getInsertID('tblDocumentReceiptLog', 'receiptLogID');
6299            return $receiptLogID;
6300        }
6301 } /* }}} */
6302
6303    /**
6304     * Add a revision to the document content
6305     *
6306     * This method will add an entry to the table tblDocumentRevisionLog.
6307     * It will first check if the user is ment to revision the document version.
6308     * If not the return value is -3.
6309     * Next it will check if the user has been removed from the list of
6310     * recipients. In that case -4 will be returned.
6311     * If the given revision has been set by the user before, it cannot
6312     * be set again and 0 will be returned. Ð†f the revision could be succesfully
6313     * added, the revision log id will be returned.
6314     *
6315     * @see SeedDMS_Core_DocumentContent::setApprovalByInd()
6316     * @param object $user user doing the revision
6317     * @param object $requestUser user asking for the revision, this is mostly
6318     * the user currently logged in.
6319     * @param integer $status the status of the revision, possible values are
6320     *        0=unprocessed (may be used to reset a status)
6321     *        1=revised,
6322     *       -2=user is deleted (use {link
6323     *       SeedDMS_Core_DocumentContent::delIndRecipient} instead)
6324     *       -3=workflow revision is sleeping
6325     * @return integer new revision log id, 0, or a value < 0. 0 means the
6326     * status has not changed because the new status is equal the current
6327     * status. A value < 0 indicate
6328     * an error. -1: internal error, -3: user may not revise this document
6329     * -4: the user has been removed from the list of revisors,
6330     * -5: the revision has not been started at all.
6331     */
6332    function setRevision($object, $requestUser, $status, $comment) { /* {{{ */
6333        $dms = $this->_document->getDMS();
6334        $db = $dms->getDB();
6335
6336        if(!$object || !$requestUser)
6337            return -1;
6338
6339        // Check to see if the user/group can be removed from the review list.
6340        $revisionStatus = $object->getRevisionStatus($this->_document->getID(), $this->_version);
6341        if (is_bool($revisionStatus) && !$revisionStatus) {
6342            return -1;
6343        }
6344
6345        /* getRevisionStatus() returns an array with either an element
6346         * 'indstatus' (user) or 'status' (group) containing the revision log
6347         */
6348        if(get_class($object) == $dms->getClassname('user')) {
6349            $revisionStatus = $revisionStatus['indstatus'];
6350        } elseif(get_class($object) == $dms->getClassname('group')) {
6351        } else {
6352            return -1;
6353        }
6354
6355        if (!$revisionStatus) {
6356            // User is not assigned to revision this document. No action required.
6357            // Return an error.
6358            return -3;
6359        }
6360        $indstatus = array_pop($revisionStatus);
6361
6362        /* check if revision workflow has been started already */
6363        if($indstatus['status'] == S_LOG_SLEEPING && ($status == S_LOG_REJECTED || $status == S_LOG_ACCEPTED))
6364            return -5;
6365
6366        if ($indstatus["status"] == -2) {
6367            // User has been deleted from recipients
6368            return -4;
6369        }
6370        // Check if the status is really different from the current status
6371        if ($indstatus["status"] == $status)
6372            return 0;
6373
6374        $queryStr = "INSERT INTO `tblDocumentRevisionLog` (`revisionID`, `status`,
6375            `comment`, `date`, `userID`) ".
6376            "VALUES ('". $indstatus["revisionID"] ."', '".
6377            (int) $status ."', ".$db->qstr($comment).", ".$db->getCurrentDatetime().", '".
6378            $requestUser->getID() ."')";
6379        $res=$db->getResult($queryStr);
6380        if (is_bool($res) && !$res)
6381            return -1;
6382        else {
6383            $revisionLogID = $db->getInsertID('tblDocumentRevisionLog', 'revisionLogID');
6384            return $revisionLogID;
6385        }
6386 } /* }}} */
6387
6388    function setRevisionByInd($user, $requestUser, $status, $comment) { /* {{{ */
6389        if(!$user || !$user->isType('user'))
6390            return -1;
6391
6392        return self::setRevision($user, $requestUser, $status, $comment);
6393    } /* }}} */
6394
6395    function setRevisionByGrp($group, $requestUser, $status, $comment) { /* {{{ */
6396        if(!$group || !$group->isType('group'))
6397            return -1;
6398
6399        return self::setRevision($group, $requestUser, $status, $comment);
6400    } /* }}} */
6401
6402    public function delIndReviewer($user, $requestUser, $msg='') { /* {{{ */
6403        $db = $this->_document->getDMS()->getDB();
6404
6405        if(!$user || !$user->isType('user'))
6406            return -1;
6407
6408        // Check to see if the user can be removed from the review list.
6409        $reviewStatus = $user->getReviewStatus($this->_document->getID(), $this->_version);
6410        if (is_bool($reviewStatus) && !$reviewStatus) {
6411            return false;
6412        }
6413        if (count($reviewStatus["indstatus"])==0) {
6414            // User is not assigned to review this document. No action required.
6415            // Return an error.
6416            return -2;
6417        }
6418        $indstatus = array_pop($reviewStatus["indstatus"]);
6419        if ($indstatus["status"]!=0) {
6420            // User has already submitted a review or has already been deleted;
6421            // return an error.
6422            return -3;
6423        }
6424
6425        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
6426            "VALUES ('". $indstatus["reviewID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6427        $res = $db->getResult($queryStr);
6428        if (is_bool($res) && !$res) {
6429            return false;
6430        }
6431
6432        return 0;
6433    } /* }}} */
6434
6435    public function delGrpReviewer($group, $requestUser, $msg='') { /* {{{ */
6436        $db = $this->_document->getDMS()->getDB();
6437
6438        if(!$group->isType('group'))
6439            return -1;
6440
6441        $groupID = $group->getID();
6442
6443        // Check to see if the user can be removed from the review list.
6444        $reviewStatus = $group->getReviewStatus($this->_document->getID(), $this->_version);
6445        if (is_bool($reviewStatus) && !$reviewStatus) {
6446            return false;
6447        }
6448        if (count($reviewStatus)==0) {
6449            // User is not assigned to review this document. No action required.
6450            // Return an error.
6451            return -2;
6452        }
6453        if ($reviewStatus[0]["status"]!=0) {
6454            // User has already submitted a review or has already been deleted;
6455            // return an error.
6456            return -3;
6457        }
6458
6459        $queryStr = "INSERT INTO `tblDocumentReviewLog` (`reviewID`, `status`, `comment`, `date`, `userID`) ".
6460            "VALUES ('". $reviewStatus[0]["reviewID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6461        $res = $db->getResult($queryStr);
6462        if (is_bool($res) && !$res) {
6463            return false;
6464        }
6465
6466        return 0;
6467    } /* }}} */
6468
6469    public function delIndApprover($user, $requestUser, $msg='') { /* {{{ */
6470        $db = $this->_document->getDMS()->getDB();
6471
6472        if(!$user->isType('user'))
6473            return -1;
6474
6475        $userID = $user->getID();
6476
6477        // Check if the user is on the approval list at all.
6478        $approvalStatus = $user->getApprovalStatus($this->_document->getID(), $this->_version);
6479        if (is_bool($approvalStatus) && !$approvalStatus) {
6480            return false;
6481        }
6482        if (count($approvalStatus["indstatus"])==0) {
6483            // User is not assigned to approve this document. No action required.
6484            // Return an error.
6485            return -2;
6486        }
6487        $indstatus = array_pop($approvalStatus["indstatus"]);
6488        if ($indstatus["status"]!=0) {
6489            // User has already submitted an approval or has already been deleted;
6490            // return an error.
6491            return -3;
6492        }
6493
6494        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
6495            "VALUES ('". $indstatus["approveID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6496        $res = $db->getResult($queryStr);
6497        if (is_bool($res) && !$res) {
6498            return false;
6499        }
6500
6501        return 0;
6502    } /* }}} */
6503
6504    public function delGrpApprover($group, $requestUser, $msg='') { /* {{{ */
6505        $db = $this->_document->getDMS()->getDB();
6506
6507        if(!$group->isType('group'))
6508            return -1;
6509
6510        $groupID = $group->getID();
6511
6512        // Check if the group is on the approval list at all.
6513        $approvalStatus = $group->getApprovalStatus($this->_document->getID(), $this->_version);
6514        if (is_bool($approvalStatus) && !$approvalStatus) {
6515            return false;
6516        }
6517        if (count($approvalStatus)==0) {
6518            // User is not assigned to approve this document. No action required.
6519            // Return an error.
6520            return -2;
6521        }
6522        if ($approvalStatus[0]["status"]!=0) {
6523            // User has already submitted an approval or has already been deleted;
6524            // return an error.
6525            return -3;
6526        }
6527
6528        $queryStr = "INSERT INTO `tblDocumentApproveLog` (`approveID`, `status`, `comment`, `date`, `userID`) ".
6529            "VALUES ('". $approvalStatus[0]["approveID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6530        $res = $db->getResult($queryStr);
6531        if (is_bool($res) && !$res) {
6532            return false;
6533        }
6534
6535        return 0;
6536    } /* }}} */
6537
6538    function delIndRecipient($user, $requestUser, $msg='') { /* {{{ */
6539        $db = $this->_document->getDMS()->getDB();
6540
6541        $userID = $user->getID();
6542
6543        // Check to see if the user can be removed from the recipient list.
6544        $receiptStatus = $user->getReceiptStatus($this->_document->getID(), $this->_version);
6545        if (is_bool($receiptStatus) && !$receiptStatus) {
6546            return -1;
6547        }
6548        if (count($receiptStatus["indstatus"])==0) {
6549            // User is not assigned to receipt this document. No action required.
6550            // Return an error.
6551            return -2;
6552        }
6553        $indstatus = array_pop($receiptStatus["indstatus"]);
6554        if ($indstatus["status"]!=0) {
6555            // User has already submitted a receipt or has already been deleted;
6556            // return an error.
6557            return -3;
6558        }
6559
6560        $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`, `comment`, `date`, `userID`) ".
6561            "VALUES ('". $indstatus["receiptID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6562        $res = $db->getResult($queryStr);
6563        if (is_bool($res) && !$res) {
6564            return -1;
6565        }
6566
6567        return 0;
6568    } /* }}} */
6569
6570    function delGrpRecipient($group, $requestUser, $msg='') { /* {{{ */
6571        $db = $this->_document->getDMS()->getDB();
6572
6573        $groupID = $group->getID();
6574
6575        // Check to see if the user can be removed from the recipient list.
6576        $receiptStatus = $group->getReceiptStatus($this->_document->getID(), $this->_version);
6577        if (is_bool($receiptStatus) && !$receiptStatus) {
6578            return -1;
6579        }
6580        if (count($receiptStatus)==0) {
6581            // User is not assigned to receipt this document. No action required.
6582            // Return an error.
6583            return -2;
6584        }
6585        $status = array_pop($receiptStatus);
6586        if ($status["status"]!=0) {
6587            // User has already submitted a receipt or has already been deleted;
6588            // return an error.
6589            return -3;
6590        }
6591
6592        $queryStr = "INSERT INTO `tblDocumentReceiptLog` (`receiptID`, `status`, `comment`, `date`, `userID`) ".
6593            "VALUES ('". $status["receiptID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6594        $res = $db->getResult($queryStr);
6595        if (is_bool($res) && !$res) {
6596            return -1;
6597        }
6598
6599        return 0;
6600    } /* }}} */
6601
6602    /**
6603     * Removes a user from the revision workflow
6604     *
6605     * This methods behaves differently from one in the other workflows, e.g.
6606     * {@see SeedDMS_Core_DocumentContent::delIndReviewer}, because it
6607     * also takes into account if the workflow has been started already.
6608     * A workflow has been started, when there are entries in the revision log.
6609     * If the revision workflow has not been started, then the user will
6610     * be silently removed from the list of revisors. If the workflow has
6611     * started already, then log entry will indicated the removal of the
6612     * user (just as it is done with the other workflows)
6613     *
6614     * @param object $object user/group which is to be removed
6615     * @param object $requestUser user requesting the removal
6616     * @return integer 0 if removal was successfull, -1 if an internal error
6617     * occured, -3 if the user is not in the list of revisors
6618     *
6619     */
6620    function delRevisor($object, $requestUser, $msg='') { /* {{{ */
6621        $dms = $this->_document->getDMS();
6622        $db = $dms->getDB();
6623
6624        // Check to see if the user/group can be removed from the revisor list.
6625        $revisionStatus = $object->getRevisionStatus($this->_document->getID(), $this->_version);
6626        if (is_bool($revisionStatus) && !$revisionStatus) {
6627            return -1;
6628        }
6629
6630        /* getRevisionStatus() returns an array with either an element
6631         * 'indstatus' (user) or no element (group) containing the revision log
6632         */
6633        if(get_class($object) == $dms->getClassname('user')) {
6634            $revisionStatus = $revisionStatus['indstatus'];
6635            $type = 0;
6636        } elseif(get_class($object) == $dms->getClassname('group')) {
6637            $type = 1;
6638        } else {
6639            return -1;
6640        }
6641
6642        if (!$revisionStatus) {
6643            // User is not assigned to revision this document. No action required.
6644            // Return an error.
6645            return -2;
6646        }
6647
6648        /* If the revision log doesn't contain an entry yet, then remove the
6649         * user/group from the list of revisors. The first case should not happen.
6650         */
6651        if(count($revisionStatus) == 0) {
6652            $queryStr = "DELETE from `tblDocumentRevisors` WHERE `documentID` = ". $this->_document->getID() ." AND `version` = ".$this->_version." AND `type` = ". $type ." AND `required` = ".$object->getID();
6653            if (!$db->getResult($queryStr)) {
6654                return -1;
6655            }
6656        } else {
6657            $indstatus = array_pop($revisionStatus);
6658            if ($indstatus["status"] != S_LOG_WAITING && $indstatus["status"] != S_LOG_SLEEPING) {
6659                // User has already submitted a revision or has already been deleted;
6660                // return an error.
6661                if($indstatus["status"] == S_LOG_USER_REMOVED)
6662                    return -3;
6663                else
6664                    return -4;
6665            }
6666
6667            $queryStr = "INSERT INTO `tblDocumentRevisionLog` (`revisionID`, `status`, `comment`, `date`, `userID`) ".
6668                "VALUES ('". $indstatus["revisionID"] ."', '".S_LOG_USER_REMOVED."', ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '". $requestUser->getID() ."')";
6669            $res = $db->getResult($queryStr);
6670            if (is_bool($res) && !$res) {
6671                return -1;
6672            }
6673        }
6674
6675        return 0;
6676    } /* }}} */
6677
6678    function delIndRevisor($user, $requestUser, $msg='') { /* {{{ */
6679        return self::delRevisor($user, $requestUser, $msg);
6680    } /* }}} */
6681
6682    function delGrpRevisor($group, $requestUser, $msg='') { /* {{{ */
6683        return self::delRevisor($group, $requestUser, $msg);
6684    } /* }}} */
6685
6686    /**
6687     * Start a new revision workflow
6688     *
6689     * This function starts a new revision unless there are users/groups
6690     * having finished the previous revision. This means the log status
6691     * must be S_LOG_SLEEPING or the user/group was removed (S_LOG_USER_REMOVED)
6692     *
6693     * @param object $requestUser user requesting the revision start
6694     * @param string $msg message saved for the initial log message
6695     */
6696    function startRevision($requestUser, $msg='') { /* {{{ */
6697        $dms = $this->_document->getDMS();
6698        $db = $dms->getDB();
6699
6700        $revisionStatus = self::getRevisionStatus();
6701        if(!$revisionStatus)
6702            return false;
6703
6704        /* A new revision may only be started if we are not in the middle of
6705         * revision or the user/group has been removed from the workflow
6706         */
6707        /* Taken out, because it happened that a revision wasn't started for each revisor
6708         * but just for some.
6709         * Checking for each revisor not being sleeping prevented a second start of the
6710         * revision for the remaining revisors still sleeping.
6711        foreach($revisionStatus as $status) {
6712            if($status['status'] != S_LOG_SLEEPING && $status['status'] != S_LOG_USER_REMOVED)
6713                return false;
6714        }
6715         */
6716
6717        /* Make sure all Logs will be set to the right status, in order to
6718         * prevent inconsistent states. Actually it could be a feature to
6719         * force only some users/groups to revise the document, but for now
6720         * this may not be possible.
6721         */
6722        $db->startTransaction();
6723        $startedrev = false;
6724        foreach($revisionStatus as $status) {
6725            if($status['status'] == S_LOG_SLEEPING) {
6726                $queryStr = "INSERT INTO `tblDocumentRevisionLog` (`revisionID`, `status`,
6727                    `comment`, `date`, `userID`) ".
6728                    "VALUES ('". $status["revisionID"] ."', ".
6729                    S_LOG_WAITING.", ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '".
6730                    $requestUser->getID() ."')";
6731                $res=$db->getResult($queryStr);
6732                if (is_bool($res) && !$res) {
6733                    $db->rollbackTransaction();
6734                    return false;
6735                }
6736                $startedrev = true;
6737            }
6738        }
6739        /* Set status only if at least one revision was started */
6740        if($startedrev)
6741            if(!$this->setStatus(S_IN_REVISION, "Started revision scheduled for ".$this->getRevisionDate(), $requestUser)) {
6742                $db->rollbackTransaction();
6743                return false;
6744            }
6745        $db->commitTransaction();
6746        return true;
6747
6748    } /* }}} */
6749
6750    /**
6751     * Finish a revision workflow
6752     *
6753     * This function ends a revision This means the log status
6754     * is set back S_LOG_SLEEPING and the document status is set as
6755     * passed to the method. The function doesn't not check if all
6756     * users/groups has made it vote already.
6757     *
6758     * @param object $requestUser user requesting the revision start
6759     * @param integer $docstatus document status
6760     * @param string $msg message saved in revision log
6761     * @param string $msg message saved in document status log
6762     */
6763    function finishRevision($requestUser, $docstatus, $msg='', $docmsg='') { /* {{{ */
6764        $dms = $this->_document->getDMS();
6765        $db = $dms->getDB();
6766
6767        $revisionStatus = self::getRevisionStatus();
6768        if(!$revisionStatus)
6769            return false;
6770
6771        /* A revision may only be finished if it wasn't finished already
6772         */
6773        foreach($revisionStatus as $status) {
6774            if($status['status'] == S_LOG_SLEEPING)
6775                return false;
6776        }
6777
6778        /* Make sure all Logs will be set to the right status, in order to
6779         * prevent inconsistent states. Actually it could be a feature to
6780         * end only some users/groups to revise the document, but for now
6781         * this may not be possible.
6782         */
6783        $db->startTransaction();
6784        /* Does it make sense to put all revisions into sleeping mode? I guess
6785         * not. If a document was released or rejected the revision are useless
6786         * anyway 
6787         */
6788        foreach($revisionStatus as $status) {
6789            if($status['status'] != S_LOG_SLEEPING && $status['status'] != S_LOG_USER_REMOVED) {
6790                $queryStr = "INSERT INTO `tblDocumentRevisionLog` (`revisionID`, `status`,
6791                    `comment`, `date`, `userID`) ".
6792                    "VALUES ('". $status["revisionID"] ."', ".
6793                    S_LOG_SLEEPING.", ".$db->qstr($msg).", ".$db->getCurrentDatetime().", '".
6794                    $requestUser->getID() ."')";
6795                $res=$db->getResult($queryStr);
6796                if (is_bool($res) && !$res) {
6797                    $db->rollbackTransaction();
6798                    return false;
6799                }
6800            }
6801        }
6802        if(!$this->setStatus($docstatus, $docmsg, $requestUser)) {
6803            $db->rollbackTransaction();
6804            return false;
6805        }
6806        $db->commitTransaction();
6807        return true;
6808
6809    } /* }}} */
6810
6811    /**
6812     * Set state of workflow assigned to the document content
6813     *
6814     * @param object $state
6815     */
6816    public function setWorkflowState($state) { /* {{{ */
6817        $db = $this->_document->getDMS()->getDB();
6818
6819        if($this->_workflow) {
6820            $queryStr = "UPDATE `tblWorkflowDocumentContent` set `state`=". $state->getID() ." WHERE `id`=". $this->_workflow['id'];
6821            if (!$db->getResult($queryStr)) {
6822                return false;
6823            }
6824            $this->_workflowState = $state;
6825            return true;
6826        }
6827        return false;
6828    } /* }}} */
6829
6830    /**
6831     * Get state of workflow assigned to the document content
6832     *
6833     * @return object/boolean an object of class SeedDMS_Core_Workflow_State
6834     *         or false in case of error, e.g. the version has not a workflow
6835     */
6836    public function getWorkflowState() { /* {{{ */
6837        $db = $this->_document->getDMS()->getDB();
6838
6839        if(!$this->_workflow)
6840            $this->getWorkflow();
6841
6842        if(!$this->_workflow)
6843            return false;
6844
6845        if (!$this->_workflowState) {
6846            $queryStr=
6847                "SELECT b.* FROM `tblWorkflowDocumentContent` a LEFT JOIN `tblWorkflowStates` b ON a.`state` = b.id WHERE a.`state` IS NOT NULL AND `a`.`id`=". $this->_workflow['id'];
6848            $recs = $db->getResultArray($queryStr);
6849            if (!$recs)
6850                return false;
6851            $this->_workflowState = new SeedDMS_Core_Workflow_State($recs[0]['id'], $recs[0]['name'], $recs[0]['maxtime'], $recs[0]['precondfunc'], $recs[0]['documentstatus']);
6852            $this->_workflowState->setDMS($this->_document->getDMS());
6853        }
6854        return $this->_workflowState;
6855    } /* }}} */
6856
6857    /**
6858     * Assign a workflow to a document content
6859     *
6860     * @param object $workflow
6861     */
6862    public function setWorkflow($workflow, $user) { /* {{{ */
6863        $db = $this->_document->getDMS()->getDB();
6864
6865        $this->getWorkflow();
6866        if($this->_workflow)
6867            return false;
6868
6869        if($workflow && is_object($workflow)) {
6870            $db->startTransaction();
6871            $initstate = $workflow->getInitState();
6872            $queryStr = "INSERT INTO `tblWorkflowDocumentContent` (`workflow`, `document`, `version`, `state`, `date`) VALUES (". $workflow->getID(). ", ". $this->_document->getID() .", ". $this->_version .", ".$initstate->getID().", ".$db->getCurrentDatetime().")";
6873            if (!$db->getResult($queryStr)) {
6874                $db->rollbackTransaction();
6875                return false;
6876            }
6877            $this->getWorkflow();
6878            if($workflow->getID() != $this->_workflow['workflow']->getID()) {
6879                $db->rollbackTransaction();
6880                return false;
6881            }
6882            if(!$this->setStatus(S_IN_WORKFLOW, "Added workflow '".$workflow->getName()."'", $user)) {
6883                $db->rollbackTransaction();
6884                return false;
6885            }
6886            $db->commitTransaction();
6887            return true;
6888        }
6889        return false;
6890    } /* }}} */
6891
6892    /**
6893     * Get workflow assigned to the document content
6894     *
6895     * The method returns the last workflow if one was assigned.
6896     * If the document version is in a sub workflow, it will have
6897     * a never date and therefore will be found first.
6898     * The methods also sets $this->_workflow['id'] and
6899     * $this->_workflow['parent']. $this->_workflow['id'] is the
6900     * id from table tblWorkflowDocumentContent which is used to
6901     * get log entries for this workflow.
6902     * This method will only get a currently running workflow in
6903     * a state. Once a
6904     * workflow has ended, the current state of the workflow was
6905     * set to null.
6906     *
6907     * @param bool $full return not just workflow but the data from
6908     *        tblWorkflowDocumentContent too
6909     * @return object/boolean an object of class SeedDMS_Core_Workflow
6910     *         or false in case of error, e.g. the version has not a workflow
6911     */
6912    public function getWorkflow($full = false) { /* {{{ */
6913        $db = $this->_document->getDMS()->getDB();
6914
6915        if (!$this->_workflow) {
6916            $queryStr=
6917                "SELECT a.`id` as `wdcid`, a.`parent`, a.`date`, b.* FROM `tblWorkflowDocumentContent` a LEFT JOIN `tblWorkflows` b ON a.`workflow` = b.`id` WHERE a.`version`='".$this->_version
6918                ."' AND a.`document` = '". $this->_document->getID() ."' "
6919                ." AND a.`state` IS NOT NULL"
6920                ." ORDER BY `date` DESC LIMIT 1";
6921            $recs = $db->getResultArray($queryStr);
6922            if (is_bool($recs) && !$recs)
6923                return false;
6924            if(!$recs)
6925                return false;
6926            $this->_workflow = array('id'=>(int)$recs[0]['wdcid'], 'parent'=>(int)$recs[0]['parent'], 'date'=>$recs[0]['date'], 'workflow'=>new SeedDMS_Core_Workflow($recs[0]['id'], $recs[0]['name'], $this->_document->getDMS()->getWorkflowState($recs[0]['initstate']), $recs[0]["layoutdata"]));
6927            $this->_workflow['workflow']->setDMS($this->_document->getDMS());
6928        }
6929        if($full)
6930            return $this->_workflow;
6931        else
6932            return $this->_workflow['workflow'];
6933    } /* }}} */
6934
6935    /**
6936     * Rewrites the complete workflow log
6937     *
6938     * Attention: this function is highly dangerous.
6939     * It removes an existing workflow log and rewrites it.
6940     * This method was added for importing an xml dump.
6941     *
6942     * @param array $workflowlog new workflow log with the newest log entry first.
6943     * @return boolean true on success, otherwise false
6944     */
6945    public function rewriteWorkflowLog($workflowlog) { /* {{{ */
6946        $db = $this->_document->getDMS()->getDB();
6947
6948        /* Get the workflowdocumentcontent */
6949        $queryStr = "SELECT `id` FROM `tblWorkflowDocumentContent` WHERE `tblWorkflowDocumentContent`.`document` = '". $this->_document->getID() ."' AND `tblWorkflowDocumentContent`.`version` = '". $this->_version ."'";
6950        $recs = $db->getResultArray($queryStr);
6951        if (is_bool($recs) && !$recs)
6952            return false;
6953        if (!$recs)
6954            return false;
6955
6956        $db->startTransaction();
6957
6958        /* First, remove the old entries */
6959        $queryStr = "DELETE FROM `tblWorkflowLog` WHERE `tblWorkflowLog`.`workflowdocumentcontent` IN (SELECT `id` FROM `tblWorkflowDocumentContent` WHERE `tblWorkflowDocumentContent`.`document` = '". $this->_document->getID() ."' AND `tblWorkflowDocumentContent`.`version` = '". $this->_version ."')";
6960        if (!$db->getResult($queryStr)) {
6961            $db->rollbackTransaction();
6962            return false;
6963        }
6964
6965        /* Second, insert the new entries */
6966        $workflowlog = array_reverse($workflowlog);
6967        foreach($workflowlog as $log) {
6968            if(!SeedDMS_Core_DMS::checkDate($log['date'], 'Y-m-d H:i:s')) {
6969                $db->rollbackTransaction();
6970                return false;
6971            }
6972            $queryStr = "INSERT INTO `tblWorkflowLog` (`workflowdocumentcontent`, `transition`, `comment`, `date`, `userid`) ".
6973                "VALUES ('".$recs[0]['id'] ."', '".(int) $log['transition']->getID()."', ".$db->qstr($log['comment']) .", ".$db->qstr($log['date']).", ".$log['user']->getID().")";
6974            if (!$db->getResult($queryStr)) {
6975                $db->rollbackTransaction();
6976                return false;
6977            }
6978        }
6979
6980        $db->commitTransaction();
6981        return true;
6982    } /* }}} */
6983
6984    /**
6985     * Restart workflow from its initial state
6986     *
6987     * @return boolean true if workflow could be restarted
6988     *         or false in case of error
6989     */
6990    public function rewindWorkflow() { /* {{{ */
6991        $db = $this->_document->getDMS()->getDB();
6992
6993        $this->getWorkflow();
6994
6995        if (!$this->_workflow) {
6996            return true;
6997        }
6998        $workflow = $this->_workflow['workflow'];
6999
7000        $db->startTransaction();
7001        $queryStr = "DELETE from `tblWorkflowLog` WHERE `workflowdocumentcontent` = ".$this->_workflow['id'];
7002        if (!$db->getResult($queryStr)) {
7003            $db->rollbackTransaction();
7004            return false;
7005        }
7006
7007        $this->setWorkflowState($workflow->getInitState());
7008        $db->commitTransaction();
7009
7010        return true;
7011    } /* }}} */
7012
7013    /**
7014     * Remove workflow
7015     *
7016     * Fully removing a workflow including entries in the workflow log is
7017     * only allowed if the workflow is still its initial state.
7018     * At a later point of time only unlinking the document from the
7019     * workflow is allowed. It will keep any log entries and set the state
7020     * to NULL.
7021     * A workflow is unlinked from a document when enterNextState()
7022     * succeeds.
7023     *
7024     * @param object $user user doing initiating the removal
7025     * @param boolean $unlink if true, just unlink the workflow from the
7026     *        document but do not remove the workflow log. The $unlink
7027     *        flag has been added to detach the workflow from the document
7028     *        when it has reached a valid end state
7029              (see SeedDMS_Core_DocumentContent::enterNextState())
7030     * @return boolean true if workflow could be removed
7031     *         or false in case of error
7032     */
7033    public function removeWorkflow($user, $unlink=false) { /* {{{ */
7034        $db = $this->_document->getDMS()->getDB();
7035
7036        $this->getWorkflow();
7037
7038        if (!$this->_workflow) {
7039            return true;
7040        }
7041
7042        $workflow = $this->_workflow['workflow'];
7043
7044        /* A workflow should always be in a state, but in case it isn't, the
7045         * at least allow to remove the workflow.
7046         */
7047        $currentstate = $this->getWorkflowState();
7048        if(!$currentstate || SeedDMS_Core_DMS::checkIfEqual($workflow->getInitState(), $currentstate) || $unlink == true) {
7049            $db->startTransaction();
7050            if($unlink) {
7051                $queryStr=
7052                    "UPDATE `tblWorkflowDocumentContent` SET `state` = NULL WHERE `id`=".$this->_workflow['id'];
7053                if (!$db->getResult($queryStr)) {
7054                    $db->rollbackTransaction();
7055                    return false;
7056                }
7057            } else {
7058                $queryStr=
7059                    "DELETE FROM `tblWorkflowDocumentContent` WHERE `id`=".$this->_workflow['id'];
7060                if (!$db->getResult($queryStr)) {
7061                    $db->rollbackTransaction();
7062                    return false;
7063                }
7064                /* will be deleted automatically when tblWorkflowDocumentContent is deleted
7065                $queryStr=
7066                    "DELETE FROM `tblWorkflowLog` WHERE "
7067                    ."`version`='".$this->_version."' "
7068                    ." AND `document` = '". $this->_document->getID() ."' "
7069                    ." AND `workflow` = '". $workflow->getID() ."' ";
7070                if (!$db->getResult($queryStr)) {
7071                    $db->rollbackTransaction();
7072                    return false;
7073                }
7074                 */
7075            }
7076            $this->_workflow = null;
7077            $this->_workflowState = null;
7078            $this->verifyStatus(false, $user, 'Workflow removed');
7079            $db->commitTransaction();
7080        }
7081
7082        return true;
7083    } /* }}} */
7084
7085    /**
7086     * Run a sub workflow
7087     *
7088     * @param object $subworkflow
7089     */
7090    public function getParentWorkflow() { /* {{{ */
7091        $db = $this->_document->getDMS()->getDB();
7092
7093        /* document content must be in a workflow */
7094        $this->getWorkflow();
7095        if(!$this->_workflow)
7096            return false;
7097
7098        if(!$this->_workflow['parent'])
7099            return false;
7100
7101        $queryStr=
7102            "SELECT * FROM `tblWorkflowDocumentContent` WHERE `parent`=".$this->_workflow['parent'];
7103        $recs = $db->getResultArray($queryStr);
7104        if (is_bool($recs) && !$recs)
7105            return false;
7106        if(!$recs)
7107            return false;
7108
7109        if($recs[0]['workflow'])
7110            return $this->_document->getDMS()->getWorkflow((int)$recs[0]['workflow']);
7111
7112        return false;
7113    } /* }}} */
7114
7115    /**
7116     * Run a sub workflow
7117     *
7118     * @param object $subworkflow
7119     */
7120    public function runSubWorkflow($subworkflow) { /* {{{ */
7121        $db = $this->_document->getDMS()->getDB();
7122
7123        /* document content must be in a workflow */
7124        $this->getWorkflow();
7125        if(!$this->_workflow)
7126            return false;
7127
7128        /* The current workflow state must match the sub workflows initial state */
7129        if($subworkflow->getInitState()->getID() != $this->_workflowState->getID())
7130            return false;
7131
7132        if($subworkflow) {
7133            $initstate = $subworkflow->getInitState();
7134            $queryStr = "INSERT INTO `tblWorkflowDocumentContent` (`parent`, `workflow`, `document`, `version`, `state`, `date`) VALUES (". $this->_workflow['id']. ", ". $subworkflow->getID(). ", ". $this->_document->getID() .", ". $this->_version .", ".$initstate->getID().", ".$db->getCurrentDatetime().")";
7135            if (!$db->getResult($queryStr)) {
7136                return false;
7137            }
7138            $this->_workflow = array('id'=>$db->getInsertID('tblWorkflowDocumentContent'),  'parent'=>$this->_workflow['id'], 'workflow'=>$subworkflow);
7139            return true;
7140        }
7141        return true;
7142    } /* }}} */
7143
7144    /**
7145     * Return from sub workflow to parent workflow.
7146     * The method will trigger the given transition
7147     *
7148     * FIXME: Needs much better checking if this is allowed
7149     *
7150     * @param object $user intiating the return
7151     * @param object $transtion to trigger
7152     * @param string comment for the transition trigger
7153     */
7154    public function returnFromSubWorkflow($user, $transition=null, $comment='') { /* {{{ */
7155        $db = $this->_document->getDMS()->getDB();
7156
7157        /* document content must be in a workflow */
7158        $this->getWorkflow();
7159        if(!$this->_workflow)
7160            return false;
7161
7162        if ($this->_workflow) {
7163            $db->startTransaction();
7164
7165            $queryStr = "UPDATE `tblWorkflowDocumentContent` SET `state` = NULL WHERE `id` = '" . $this->_workflow['id']."'";
7166            if (!$db->getResult($queryStr)) {
7167                $db->rollbackTransaction();
7168                return false;
7169            }
7170
7171            /* Calling getWorkflow() should find the parent workflow, better check */
7172            $parent = $this->_workflow['parent'];
7173            $this->_workflow = null;
7174            $this->getWorkflow();
7175            if($this->_workflow['id'] != $parent) {
7176                $db->rollbackTransaction();
7177                return false;
7178            }
7179
7180            if($transition) {
7181                if(false === $this->triggerWorkflowTransition($user, $transition, $comment)) {
7182                    $db->rollbackTransaction();
7183                    return false;
7184                }
7185            }
7186
7187            $db->commitTransaction();
7188        }
7189        return $this->_workflow['workflow'];
7190    } /* }}} */
7191
7192    /**
7193     * Check if the user is allowed to trigger the transition
7194     * A user is allowed if either the user itself or
7195     * a group of which the user is a member of is registered for
7196     * triggering a transition. This method does not change the workflow
7197     * state of the document content.
7198     *
7199     * @param object $user
7200     * @return boolean true if user may trigger transaction
7201     */
7202    public function triggerWorkflowTransitionIsAllowed($user, $transition) { /* {{{ */
7203        $db = $this->_document->getDMS()->getDB();
7204
7205        if(!$this->_workflow)
7206            $this->getWorkflow();
7207
7208        if(!$this->_workflow)
7209            return false;
7210
7211        if(!$this->_workflowState)
7212            $this->getWorkflowState();
7213
7214        /* Check if the user has already triggered the transition */
7215        $queryStr=
7216            "SELECT * FROM `tblWorkflowLog` WHERE `workflowdocumentcontent` = ".$this->_workflow['id']." AND userid = ".$user->getID();
7217        $queryStr .= " AND `transition` = ".$transition->getID();
7218        $resArr = $db->getResultArray($queryStr);
7219        if (is_bool($resArr) && !$resArr)
7220            return false;
7221
7222        if(count($resArr))
7223            return false;
7224
7225        /* Get all transition users allowed to trigger the transition */
7226        $transusers = $transition->getUsers();
7227        if($transusers) {
7228            foreach($transusers as $transuser) {
7229                if($user->getID() == $transuser->getUser()->getID())
7230                    return true;
7231            }
7232        }
7233
7234        /* Get all transition groups whose members are allowed to trigger
7235         * the transition */
7236        $transgroups = $transition->getGroups();
7237        if($transgroups) {
7238            foreach($transgroups as $transgroup) {
7239                $group = $transgroup->getGroup();
7240                if($group->isMember($user))
7241                    return true;
7242            }
7243        }
7244
7245        return false;
7246    } /* }}} */
7247
7248    /**
7249     * Check if all conditions are met to change the workflow state
7250     * of a document content (run the transition).
7251     * The conditions are met if all explicitly set users and a sufficient
7252     * number of users of the groups have acknowledged the content.
7253     *
7254     * @return boolean true if transaction maybe executed
7255     */
7256    public function executeWorkflowTransitionIsAllowed($transition) { /* {{{ */
7257        if(!$this->_workflow)
7258            $this->getWorkflow();
7259
7260        if(!$this->_workflow)
7261            return false;
7262
7263        if(!$this->_workflowState)
7264            $this->getWorkflowState();
7265
7266        /* Get the Log of transition triggers */
7267        $entries = $this->getWorkflowLog($transition);
7268        if(!$entries)
7269            return false;
7270
7271        /* Get all transition users allowed to trigger the transition
7272         * $allowedusers is a list of all users allowed to trigger the
7273         * transition
7274         */
7275        $transusers = $transition->getUsers();
7276        $allowedusers = array();
7277        foreach($transusers as $transuser) {
7278            $a = $transuser->getUser();
7279            $allowedusers[$a->getID()] = $a;
7280        }
7281
7282        /* Get all transition groups whose members are allowed to trigger
7283         * the transition */
7284        $transgroups = $transition->getGroups();
7285        foreach($entries as $entry) {
7286            $loguser = $entry->getUser();
7287            /* Unset each allowed user if it was found in the log */
7288            if(isset($allowedusers[$loguser->getID()]))
7289                unset($allowedusers[$loguser->getID()]);
7290            /* Also check groups if required. Count the group membership of
7291             * each user in the log in the array $gg
7292             */
7293            if($transgroups) {
7294                $loggroups = $loguser->getGroups();
7295                foreach($loggroups as $loggroup) {
7296                    if(!isset($gg[$loggroup->getID()]))
7297                        $gg[$loggroup->getID()] = 1;
7298                    else
7299                        $gg[$loggroup->getID()]++;
7300                }
7301            }
7302        }
7303        /* If there are allowed users left, then there some users still
7304         * need to trigger the transition.
7305         */
7306        if($allowedusers)
7307            return false;
7308
7309        if($transgroups) {
7310            foreach($transgroups as $transgroup) {
7311                $group = $transgroup->getGroup();
7312                $minusers = $transgroup->getNumOfUsers();
7313                if(!isset($gg[$group->getID()]))
7314                    return false;
7315                if($gg[$group->getID()] < $minusers)
7316                    return false;
7317            }
7318        }
7319        return true;
7320    } /* }}} */
7321
7322    /**
7323     * Trigger transition
7324     *
7325     * This method will be deprecated
7326     *
7327     * The method will first check if the user is allowed to trigger the
7328     * transition. If the user is allowed, an entry in the workflow log
7329     * will be added, which is later used to check if the transition
7330     * can actually be processed. The method will finally call
7331     * executeWorkflowTransitionIsAllowed() which checks all log entries
7332     * and does the transitions post function if all users and groups have
7333     * triggered the transition. Finally enterNextState() is called which
7334     * will try to enter the next state.
7335     *
7336     * @param object $user
7337     * @param object $transition
7338     * @param string $comment user comment
7339     * @return boolean/object next state if transition could be triggered and
7340     *         then next state could be entered,
7341     *         true if the transition could just be triggered or
7342     *         false in case of an error
7343     */
7344    public function triggerWorkflowTransition($user, $transition, $comment='') { /* {{{ */
7345        $db = $this->_document->getDMS()->getDB();
7346
7347        if(!$this->_workflow)
7348            $this->getWorkflow();
7349
7350        if(!$this->_workflow)
7351            return false;
7352
7353        if(!$this->_workflowState)
7354            $this->getWorkflowState();
7355
7356        if(!$this->_workflowState)
7357            return false;
7358
7359        /* Check if the user is allowed to trigger the transition.
7360         */
7361        if(!$this->triggerWorkflowTransitionIsAllowed($user, $transition))
7362            return false;
7363
7364        $queryStr = "INSERT INTO `tblWorkflowLog` (`workflowdocumentcontent`, `userid`, `transition`, `date`, `comment`) VALUES (".$this->_workflow['id'].", ".(int) $user->getID(). ", ".(int) $transition->getID().", ".$db->getCurrentDatetime().", ".$db->qstr($comment).")";
7365        if (!$db->getResult($queryStr))
7366            return false;
7367
7368        /* Check if this transition is processed. Run the post function in
7369         * that case. A transition is processed when all users and groups
7370         * have triggered it.
7371         */
7372        if($this->executeWorkflowTransitionIsAllowed($transition)) {
7373            /* run post function of transition */
7374//            echo "run post function of transition ".$transition->getID()."<br />";
7375        }
7376
7377        /* Go into the next state. This will only succeed if the pre condition
7378         * function of that states succeeds.
7379         */
7380        $nextstate = $transition->getNextState();
7381        if($this->enterNextState($user, $nextstate)) {
7382            return $nextstate;
7383        }
7384        return true;
7385
7386    } /* }}} */
7387
7388    /**
7389     * Enter next state of workflow if possible
7390     *
7391     * The method will check if one of the following states in the workflow
7392     * can be reached.
7393     * It does it by running
7394     * the precondition function of that state. The precondition function
7395     * gets a list of all transitions leading to the state. It will
7396     * determine, whether the transitions has been triggered and if that
7397     * is sufficient to enter the next state. If no pre condition function
7398     * is set, then 1 of n transtions are enough to enter the next state.
7399     *
7400     * If moving in the next state is possible and this state has a
7401     * corresponding document state, then the document state will be
7402     * updated and the workflow will be detached from the document.
7403     *
7404     * @param object $user
7405     * @param object $nextstate
7406     * @return boolean true if the state could be reached
7407     *         false if not
7408     */
7409    public function enterNextState($user, $nextstate) { /* {{{ */
7410
7411            /* run the pre condition of the next state. If it is not set
7412             * the next state will be reached if one of the transitions
7413             * leading to the given state can be processed.
7414             */
7415            if($nextstate->getPreCondFunc() == '') {
7416                $workflow = $this->_workflow['workflow'];
7417                $transitions = $workflow->getPreviousTransitions($nextstate);
7418                foreach($transitions as $transition) {
7419//                echo "transition ".$transition->getID()." led to state ".$nextstate->getName()."<br />";
7420                    if($this->executeWorkflowTransitionIsAllowed($transition)) {
7421//                    echo "stepping into next state<br />";
7422                        $this->setWorkflowState($nextstate);
7423
7424                        /* Check if the new workflow state has a mapping into a
7425                         * document state. If yes, set the document state will
7426                         * be updated and the workflow will be removed from the
7427                         * document.
7428                         */
7429                        $docstate = $nextstate->getDocumentStatus();
7430                        if($docstate == S_RELEASED || $docstate == S_REJECTED) {
7431                            $this->setStatus($docstate, "Workflow has ended", $user);
7432                            /* Detach the workflow from the document, but keep the
7433                             * workflow log
7434                             */
7435                            $this->removeWorkflow($user, true);
7436                            return true ;
7437                        }
7438
7439                        /* make sure the users and groups allowed to trigger the next
7440                         * transitions are also allowed to read the document
7441                         */
7442                        $transitions = $workflow->getNextTransitions($nextstate);
7443                        foreach($transitions as $tran) {
7444//                            echo "checking access for users/groups allowed to trigger transition ".$tran->getID()."<br />";
7445                            $transusers = $tran->getUsers();
7446                            foreach($transusers as $transuser) {
7447                                $u = $transuser->getUser();
7448//                                echo $u->getFullName()."<br />";
7449                                if($this->_document->getAccessMode($u) < M_READ) {
7450                                    $this->_document->addAccess(M_READ, $u->getID(), 1);
7451//                                    echo "granted read access<br />";
7452                                } else {
7453//                                    echo "has already access<br />";
7454                                }
7455                            }
7456                            $transgroups = $tran->getGroups();
7457                            foreach($transgroups as $transgroup) {
7458                                $g = $transgroup->getGroup();
7459//                                echo $g->getName()."<br />";
7460                                if ($this->_document->getGroupAccessMode($g) < M_READ) {
7461                                    $this->_document->addAccess(M_READ, $g->getID(), 0);
7462//                                    echo "granted read access<br />";
7463                                } else {
7464//                                    echo "has already access<br />";
7465                                }
7466                            }
7467                        }
7468                        return(true);
7469                    } else {
7470//                        echo "transition not ready for process now<br />";
7471                    }
7472                }
7473                return false;
7474            } else {
7475            }
7476
7477    } /* }}} */
7478
7479    /**
7480     * Get the so far logged operations on the document content within the
7481     * workflow. If the document content is currently in a workflow and
7482     * a transition is passed, then the
7483     * log entries will be restricted on the workflow and returned as a one
7484     * dimensional list. Without a running workflow the log entries of
7485     * all workflows in the past are returned grouped by workflow.
7486     * This result is a two dimensional array. The keys of the first
7487     * dimension are the ids used in table tblWorkflowDocumentContent.
7488     * If only the logs of last workflow run are of interesst, then just
7489     * take the last element of the returned array.
7490     *
7491     * Example: A workflow was started for a document content.
7492     * This will add an entry in tblWorkflowDocumentContent whose state is set
7493     * to the initial state of the workflow and a new autoinc id, e.g. with id 45
7494     * Once any step in the workflow was triggered, the table tblWorkflowLog will
7495     * have an entry for workflowdocumentcontent=45.
7496     * Retrieving the workflow log as long the document is still in the workflow
7497     * will return the log entries for the current workflow. In this particular
7498     * case it will be an array with one log entry.
7499     * Once the workflow has ended this method will still return the log entries
7500     * but in a 2-dimensional array with the first dimension set to 45.
7501     *
7502     * The same document version can be run through the same or a different
7503     * workflow again which will lead to a new entry in
7504     * tblWorkflowDocumentContent, e.g. with id 46.  Getting the log entries
7505     * while the content is still in the workflow will return only those entries
7506     * for the current workflow. Once the workflow has ended, this methods
7507     * returns a 2-dimensional array with two elements in the first dimension.
7508     * One for key 45 and another one for key 46.
7509     *
7510     * @return array list of objects
7511     */
7512    public function getWorkflowLog($transition = null) { /* {{{ */
7513        $db = $this->_document->getDMS()->getDB();
7514
7515        if(!$this->_workflow)
7516            $this->getWorkflow();
7517
7518        $queryStr=
7519            "SELECT `a`.`id`, `a`.`userid`, `a`.`transition`, `a`.`date`, `a`.`comment`, `a`.`workflowdocumentcontent`, `b`.`version`, `b`.`document`, `b`.`workflow` FROM `tblWorkflowLog` `a` LEFT JOIN `tblWorkflowDocumentContent` `b` ON `a`.`workflowdocumentcontent` = `b`.`id` WHERE `b`.`version`='".$this->_version ."' AND `b`.`document` = '". $this->_document->getID() ."'"; // AND `workflow` = ". $this->_workflow->getID();
7520        if($transition) {
7521            $queryStr .= " AND `a`.`transition` = ".$transition->getID();
7522        }
7523        if($this->_workflow)
7524            $queryStr .= " AND `a`.`workflowdocumentcontent` = ".$this->_workflow['id'];
7525        $queryStr .= " ORDER BY `a`.`date`";
7526        $resArr = $db->getResultArray($queryStr);
7527        if (is_bool($resArr) && !$resArr)
7528            return false;
7529
7530        $workflowlogs = array();
7531        for ($i = 0; $i < count($resArr); $i++) {
7532            $workflow = $this->_document->getDMS()->getWorkflow($resArr[$i]["workflow"]);
7533            $workflowlog = new SeedDMS_Core_Workflow_Log($resArr[$i]["id"], $this->_document->getDMS()->getDocument($resArr[$i]["document"]), $resArr[$i]["version"], $workflow, $this->_document->getDMS()->getUser($resArr[$i]["userid"]), $workflow->getTransition($resArr[$i]["transition"]), $resArr[$i]["date"], $resArr[$i]["comment"]);
7534            $workflowlog->setDMS($this);
7535            if($this->_workflow)
7536                $workflowlogs[] = $workflowlog;
7537            else
7538                $workflowlogs[$resArr[$i]["workflowdocumentcontent"]][] = $workflowlog;
7539        }
7540
7541        return $workflowlogs;
7542    } /* }}} */
7543
7544    /**
7545     * Get the latest workflow log entry for the document content within the
7546     * workflow. Even after finishing the workflow (when the document content
7547     * does not have a workflow set anymore) this function returns the last
7548     * log entry.
7549     *
7550     * @return object
7551     */
7552    public function getLastWorkflowLog() { /* {{{ */
7553        $db = $this->_document->getDMS()->getDB();
7554
7555/*
7556        if(!$this->_workflow)
7557            $this->getWorkflow();
7558
7559        if(!$this->_workflow)
7560            return false;
7561 */
7562        $queryStr=
7563            "SELECT `a`.*, `b`.`workflow`, `b`.`document`, `b`.`version` FROM `tblWorkflowLog` `a` LEFT JOIN `tblWorkflowDocumentContent` `b` ON `a`.`workflowdocumentcontent` = `b`.`id` WHERE `b`.`version`='".$this->_version ."' AND `b`.`document` = '". $this->_document->getID() ."'";
7564        $queryStr .= " ORDER BY `id` DESC LIMIT 1";
7565        $resArr = $db->getResultArray($queryStr);
7566        if (is_bool($resArr) && !$resArr)
7567            return false;
7568
7569        $i = 0;
7570        $workflow = $this->_document->getDMS()->getWorkflow($resArr[$i]["workflow"]);
7571        $workflowlog = new SeedDMS_Core_Workflow_Log($resArr[$i]["id"], $this->_document->getDMS()->getDocument($resArr[$i]["document"]), $resArr[$i]["version"], $workflow, $this->_document->getDMS()->getUser($resArr[$i]["userid"]), $workflow->getTransition($resArr[$i]["transition"]), $resArr[$i]["date"], $resArr[$i]["comment"]);
7572        $workflowlog->setDMS($this);
7573
7574        return $workflowlog;
7575    } /* }}} */
7576
7577    /**
7578     * Check if the document content needs an action by a user
7579     *
7580     * This method will return true if document content is in a transition
7581     * which can be triggered by the given user.
7582     *
7583     * @param SeedDMS_Core_User $user
7584     * @return boolean true is action is needed
7585     */
7586    public function needsWorkflowAction($user) { /* {{{ */
7587        $needwkflaction = false;
7588        if($this->_workflow) {
7589            $workflow = $this->_workflow['workflow'];
7590            if (!$this->_workflowState)
7591                $this->getWorkflowState();
7592            $workflowstate = $this->_workflowState;
7593            if($transitions = $workflow->getNextTransitions($workflowstate)) {
7594                foreach($transitions as $transition) {
7595                    if($this->triggerWorkflowTransitionIsAllowed($user, $transition)) {
7596                        $needwkflaction = true;
7597                    }
7598                }
7599            }
7600        }
7601        return $needwkflaction;
7602    } /* }}} */
7603
7604    /**
7605     * Checks the internal data of the document version and repairs it.
7606     * Currently, this function only repairs a missing filetype
7607     *
7608     * @return boolean true on success, otherwise false
7609     */
7610    public function repair() { /* {{{ */
7611        $dms = $this->_document->getDMS();
7612        $db = $this->_dms->getDB();
7613
7614        if(SeedDMS_Core_File::file_exists($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType)) {
7615            if(strlen($this->_fileType) < 2) {
7616                switch($this->_mimeType) {
7617                case "application/pdf":
7618                case "image/png":
7619                case "image/gif":
7620                case "image/jpg":
7621                    $expect = substr($this->_mimeType, -3, 3);
7622                    if($this->_fileType != '.'.$expect) {
7623                        $db->startTransaction();
7624                        $queryStr = "UPDATE `tblDocumentContent` SET `fileType`='.".$expect."' WHERE `id` = ". $this->_id;
7625                        $res = $db->getResult($queryStr);
7626                        if ($res) {
7627                            if(!SeedDMS_Core_File::renameFile($this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType, $this->_dms->contentDir.$this->_document->getDir() . $this->_version . '.' . $expect)) {
7628                                $db->rollbackTransaction();
7629                            } else {
7630                                $db->commitTransaction();
7631                            }
7632                        } else {
7633                            $db->rollbackTransaction();
7634                        }
7635                    }
7636                    break;
7637                }
7638            }
7639        } elseif(SeedDMS_Core_File::file_exists($this->_document->getDir() . $this->_version . '.')) {
7640            echo "no file";
7641        } else {
7642            echo $this->_dms->contentDir.$this->_document->getDir() . $this->_version . $this->_fileType;
7643        }
7644        return true;
7645    } /* }}} */
7646
7647} /* }}} */
7648
7649
7650/**
7651 * Class to represent a link between two document
7652 *
7653 * Document links are to establish a reference from one document to
7654 * another document. The owner of the document link may not be the same
7655 * as the owner of one of the documents.
7656 * Use {@see SeedDMS_Core_Document::addDocumentLink()} to add a reference
7657 * to another document.
7658 *
7659 * @category   DMS
7660 * @package    SeedDMS_Core
7661 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
7662 *             Uwe Steinmann <uwe@steinmann.cx>
7663 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
7664 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
7665 *             2010-2024 Uwe Steinmann
7666 * @version    Release: @package_version@
7667 */
7668class SeedDMS_Core_DocumentLink { /* {{{ */
7669    /**
7670     * @var integer internal id of document link
7671     */
7672    protected $_id;
7673
7674    /**
7675     * @var SeedDMS_Core_Document reference to document this link belongs to
7676     */
7677    protected $_document;
7678
7679    /**
7680     * @var object reference to target document this link points to
7681     */
7682    protected $_target;
7683
7684    /**
7685     * @var integer id of user who is the owner of this link
7686     */
7687    protected $_userID;
7688
7689    /**
7690     * @var object $_user user who is the owner of this link
7691     */
7692    protected $_user;
7693
7694    /**
7695     * @var integer 1 if this link is public, or 0 if is only visible to the owner
7696     */
7697    protected $_public;
7698
7699    /**
7700     * SeedDMS_Core_DocumentLink constructor.
7701     * @param $id
7702     * @param $document
7703     * @param $target
7704     * @param $userID
7705     * @param $public
7706     */
7707    public function __construct($id, $document, $target, $userID, $public) {
7708        $this->_id = $id;
7709        $this->_document = $document;
7710        $this->_target = $target;
7711        $this->_userID = $userID;
7712        $this->_user = null;
7713        $this->_public = $public ? true : false;
7714    }
7715
7716    /**
7717     * Check if this object is of type 'documentlink'.
7718     *
7719     * @param string $type type of object
7720     */
7721    public function isType($type) { /* {{{ */
7722        return $type == 'documentlink';
7723    } /* }}} */
7724
7725    /**
7726     * @return int
7727     */
7728    public function getID() { return $this->_id; }
7729
7730    /**
7731     * @return SeedDMS_Core_Document
7732     */
7733    public function getDocument() {
7734        return $this->_document;
7735    }
7736
7737    /**
7738     * @return object
7739     */
7740    public function getTarget() {
7741        return $this->_target;
7742    }
7743
7744    /**
7745     * @return bool|SeedDMS_Core_User
7746     */
7747    public function getUser() {
7748        if (!isset($this->_user)) {
7749            $this->_user = $this->_document->getDMS()->getUser($this->_userID);
7750        }
7751        return $this->_user;
7752    }
7753
7754    /**
7755     * @return int
7756     */
7757    public function isPublic() { return $this->_public; }
7758
7759    /**
7760     * Returns the access mode similar to a document
7761     *
7762     * There is no real access mode for document links, so this is just
7763     * another way to add more access restrictions than the default restrictions.
7764     * It is only called for public document links, not accessed by the owner
7765     * or the administrator.
7766     *
7767     * @param SeedDMS_Core_User $u user
7768     * @param $source
7769     * @param $target
7770     * @return int either M_NONE or M_READ
7771     */
7772    public function getAccessMode($u, $source, $target) { /* {{{ */
7773        $dms = $this->_document->getDMS();
7774
7775        /* Check if 'onCheckAccessDocumentLink' callback is set */
7776        if(isset($dms->callbacks['onCheckAccessDocumentLink'])) {
7777            foreach($dms->callbacks['onCheckAccessDocumentLink'] as $callback) {
7778                if(($ret = call_user_func($callback[0], $callback[1], $this, $u, $source, $target)) > 0) {
7779                    return $ret;
7780                }
7781            }
7782        }
7783
7784        return M_READ;
7785    } /* }}} */
7786
7787} /* }}} */
7788
7789/**
7790 * Class to represent a file attached to a document
7791 *
7792 * Beside the regular document content arbitrary files can be attached
7793 * to a document. This is a similar concept as attaching files to emails.
7794 * The owner of the attached file and the document may not be the same.
7795 * Use {@see SeedDMS_Core_Document::addDocumentFile()} to attach a file.
7796 *
7797 * @category   DMS
7798 * @package    SeedDMS_Core
7799 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
7800 *             Uwe Steinmann <uwe@steinmann.cx>
7801 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
7802 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
7803 *             2010-2024 Uwe Steinmann
7804 * @version    Release: @package_version@
7805 */
7806class SeedDMS_Core_DocumentFile { /* {{{ */
7807    /**
7808     * @var integer internal id of document file
7809     */
7810    protected $_id;
7811
7812    /**
7813     * @var SeedDMS_Core_Document reference to document this file belongs to
7814     */
7815    protected $_document;
7816
7817    /**
7818     * @var integer id of user who is the owner of this link
7819     */
7820    protected $_userID;
7821
7822    /**
7823     * @var object user who is the owner of this link
7824     */
7825    protected $_user;
7826
7827    /**
7828     * @var string comment for the attached file
7829     */
7830    protected $_comment;
7831
7832    /**
7833     * @var string date when the file was attached
7834     */
7835    protected $_date;
7836
7837    /**
7838     * @var integer version of document this file is attached to
7839     */
7840    protected $_version;
7841
7842    /**
7843     * @var integer 1 if this link is public, or 0 if is only visible to the owner
7844     */
7845    protected $_public;
7846
7847    /**
7848     * @var string directory where the file is stored. This is the
7849     * document id with a proceding '/'.
7850     * FIXME: looks like this isn't used anymore. The file path is
7851     * constructed by getPath()
7852     */
7853    protected $_dir;
7854
7855    /**
7856     * @var string extension of the original file name with a leading '.'
7857     */
7858    protected $_fileType;
7859
7860    /**
7861     * @var string mime type of the file
7862     */
7863    protected $_mimeType;
7864
7865    /**
7866     * @var string name of the file that was originally uploaded
7867     */
7868    protected $_orgFileName;
7869
7870    /**
7871     * @var string name of the file as given by the user
7872     */
7873    protected $_name;
7874
7875    /**
7876     * SeedDMS_Core_DocumentFile constructor.
7877     * @param $id
7878     * @param $document
7879     * @param $userID
7880     * @param $comment
7881     * @param $date
7882     * @param $dir
7883     * @param $fileType
7884     * @param $mimeType
7885     * @param $orgFileName
7886     * @param $name
7887     * @param $version
7888     * @param $public
7889     */
7890    public function __construct($id, $document, $userID, $comment, $date, $dir, $fileType, $mimeType, $orgFileName,$name,$version,$public) {
7891        $this->_id = $id;
7892        $this->_document = $document;
7893        $this->_userID = $userID;
7894        $this->_user = null;
7895        $this->_comment = $comment;
7896        $this->_date = $date;
7897        $this->_dir = $dir;
7898        $this->_fileType = $fileType;
7899        $this->_mimeType = $mimeType;
7900        $this->_orgFileName = $orgFileName;
7901        $this->_name = $name;
7902        $this->_version = $version;
7903        $this->_public = $public ? true : false;
7904    }
7905
7906    /**
7907     * Check if this object is of type 'documentfile'.
7908     *
7909     * @param string $type type of object
7910     */
7911    public function isType($type) { /* {{{ */
7912        return $type == 'documentfile';
7913    } /* }}} */
7914
7915    /**
7916     * @return int
7917     */
7918    public function getID() { return $this->_id; }
7919
7920    /**
7921     * @return SeedDMS_Core_Document
7922     */
7923    public function getDocument() { return $this->_document; }
7924
7925    /**
7926     * @return int
7927     */
7928    public function getUserID() { return $this->_userID; }
7929
7930    /**
7931     * @return string
7932     */
7933    public function getComment() { return $this->_comment; }
7934
7935    /*
7936     * Set the comment of the document file
7937     *
7938     * @param string $newComment string new comment of document
7939     */
7940    public function setComment($newComment) { /* {{{ */
7941        $db = $this->_document->getDMS()->getDB();
7942
7943        $queryStr = "UPDATE `tblDocumentFiles` SET `comment` = ".$db->qstr($newComment)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
7944        if (!$db->getResult($queryStr))
7945            return false;
7946
7947        $this->_comment = $newComment;
7948        return true;
7949    } /* }}} */
7950
7951    /**
7952     * @return string
7953     */
7954    public function getDate() { return $this->_date; }
7955
7956    /**
7957     * Set creation date of the document file
7958     *
7959     * @param integer $date timestamp of creation date. If false then set it
7960     * to the current timestamp
7961     * @return boolean true on success
7962     */
7963    public function setDate($date=null) { /* {{{ */
7964        $db = $this->_document->getDMS()->getDB();
7965
7966        if(!$date)
7967            $date = time();
7968        else {
7969            if(!is_numeric($date))
7970                return false;
7971        }
7972
7973        $queryStr = "UPDATE `tblDocumentFiles` SET `date` = " . (int) $date . " WHERE `id` = ". $this->_id;
7974        if (!$db->getResult($queryStr))
7975            return false;
7976        $this->_date = $date;
7977        return true;
7978    } /* }}} */
7979
7980    /**
7981     * @return string
7982     */
7983    public function getDir() { return $this->_dir; }
7984
7985    /**
7986     * @return string
7987     */
7988    public function getFileType() { return $this->_fileType; }
7989
7990    /**
7991     * @return string
7992     */
7993    public function getMimeType() { return $this->_mimeType; }
7994
7995    public function getRealMimeType() { /* {{{ */
7996        $dms = $this->_document->getDMS();
7997        if($storage = $dms->getStorage()) {
7998            $mimetype = $storage->getContentMimetype($this->_document, $this);
7999        } else {
8000            $mimetype = SeedDMS_Core_File::mimetype($dms->contentDir . $this->getPath());
8001        }
8002        return $mimetype;
8003    } /* }}} */
8004
8005    /**
8006     * @return string
8007     */
8008    public function getOriginalFileName() { return $this->_orgFileName; }
8009
8010    /**
8011     * @return string
8012     */
8013    public function getName() { return $this->_name; }
8014
8015    /*
8016     * Set the name of the document file
8017     *
8018     * @param $newComment string new name of document
8019     */
8020    public function setName($newName) { /* {{{ */
8021        $db = $this->_document->getDMS()->getDB();
8022
8023        $queryStr = "UPDATE `tblDocumentFiles` SET `name` = ".$db->qstr($newName)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
8024        if (!$db->getResult($queryStr))
8025            return false;
8026
8027        $this->_name = $newName;
8028
8029        return true;
8030    } /* }}} */
8031
8032    /**
8033     * @return bool|SeedDMS_Core_User
8034     */
8035    public function getUser() {
8036        if (!isset($this->_user))
8037            $this->_user = $this->_document->getDMS()->getUser($this->_userID);
8038        return $this->_user;
8039    }
8040
8041    /**
8042     * @return string
8043     */
8044    public function getPath() {
8045        return $this->_document->getDir() . "f" .$this->_id . $this->_fileType;
8046    }
8047
8048    /**
8049     * @return int
8050     */
8051    public function getVersion() { return $this->_version; }
8052
8053    /*
8054     * Set the version of the document file
8055     *
8056     * @param $newComment string new version of document
8057     */
8058    public function setVersion($newVersion) { /* {{{ */
8059        $db = $this->_document->getDMS()->getDB();
8060
8061        if(!is_numeric($newVersion) && $newVersion != '')
8062            return false;
8063
8064        $queryStr = "UPDATE `tblDocumentFiles` SET `version` = ".(int) $newVersion." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
8065        if (!$db->getResult($queryStr))
8066            return false;
8067
8068        $this->_version = (int) $newVersion;
8069        return true;
8070    } /* }}} */
8071
8072    /**
8073     * @return int
8074     */
8075    public function isPublic() { return $this->_public; }
8076
8077    /*
8078     * Set the public flag of the document file
8079     *
8080     * @param $newComment string new comment of document
8081     */
8082    public function setPublic($newPublic) { /* {{{ */
8083        $db = $this->_document->getDMS()->getDB();
8084
8085        $queryStr = "UPDATE `tblDocumentFiles` SET `public` = ".($newPublic ? 1 : 0)." WHERE `document` = ".$this->_document->getId()." AND `id` = ". $this->_id;
8086        if (!$db->getResult($queryStr))
8087            return false;
8088
8089        $this->_public = $newPublic ? true : false;
8090        return true;
8091    } /* }}} */
8092
8093    /**
8094     * Returns the access mode similar to a document
8095     *
8096     * There is no real access mode for document files, so this is just
8097     * another way to add more access restrictions than the default restrictions.
8098     * It is only called for public document files, not accessed by the owner
8099     * or the administrator.
8100     *
8101     * @param object $u user
8102     * @return integer either M_NONE or M_READ
8103     */
8104    public function getAccessMode($u) { /* {{{ */
8105        $dms = $this->_document->getDMS();
8106
8107        /* Check if 'onCheckAccessDocumentLink' callback is set */
8108        if(isset($this->_dms->callbacks['onCheckAccessDocumentFile'])) {
8109            foreach($this->_dms->callbacks['onCheckAccessDocumentFile'] as $callback) {
8110                if(($ret = call_user_func($callback[0], $callback[1], $this, $u)) > 0) {
8111                    return $ret;
8112                }
8113            }
8114        }
8115
8116        return M_READ;
8117    } /* }}} */
8118
8119} /* }}} */
8120
8121//
8122// Perhaps not the cleanest object ever devised, it exists to encapsulate all
8123// of the data generated during the addition of new content to the database.
8124// The object stores a copy of the new DocumentContent object, the newly assigned
8125// reviewers and approvers and the status.
8126//
8127/**
8128 * Class to represent a list of document contents
8129 *
8130 * @category   DMS
8131 * @package    SeedDMS_Core
8132 * @author     Markus Westphal, Malcolm Cowe, Matteo Lucarelli,
8133 *             Uwe Steinmann <uwe@steinmann.cx>
8134 * @copyright  Copyright (C) 2002-2005 Markus Westphal,
8135 *             2006-2008 Malcolm Cowe, 2010 Matteo Lucarelli,
8136 *             2010-2024 Uwe Steinmann
8137 * @version    Release: @package_version@
8138 */
8139class SeedDMS_Core_AddContentResultSet { /* {{{ */
8140
8141    /**
8142     * @var null
8143     */
8144    protected $_indReviewers;
8145
8146    /**
8147     * @var null
8148     */
8149    protected $_grpReviewers;
8150
8151    /**
8152     * @var null
8153     */
8154    protected $_indApprovers;
8155
8156    /**
8157     * @var null
8158     */
8159    protected $_grpApprovers;
8160
8161    /**
8162     * @var
8163     */
8164    protected $_content;
8165
8166    /**
8167     * @var null
8168     */
8169    protected $_status;
8170
8171    /**
8172     * @var SeedDMS_Core_DMS back reference to document management system
8173     */
8174    protected $_dms;
8175
8176    /**
8177     * SeedDMS_Core_AddContentResultSet constructor.
8178     * @param $content
8179     */
8180    public function __construct($content) { /* {{{ */
8181        $this->_content = $content;
8182        $this->_indReviewers = null;
8183        $this->_grpReviewers = null;
8184        $this->_indApprovers = null;
8185        $this->_grpApprovers = null;
8186        $this->_status = null;
8187        $this->_dms = null;
8188    } /* }}} */
8189
8190    /**
8191     * Set dms this object belongs to.
8192     *
8193     * Each object needs a reference to the dms it belongs to. It will be
8194     * set when the object is created.
8195     * The dms has a references to the currently logged in user
8196     * and the database connection.
8197     *
8198     * @param SeedDMS_Core_DMS $dms reference to dms
8199     */
8200    public function setDMS($dms) { /* {{{ */
8201        $this->_dms = $dms;
8202    } /* }}} */
8203
8204    /**
8205     * @param $reviewer
8206     * @param $type
8207     * @param $status
8208     * @return bool
8209     */
8210    public function addReviewer($reviewer, $type, $status) { /* {{{ */
8211        $dms = $this->_dms;
8212
8213        if (!is_object($reviewer) || (strcasecmp($type, "i") && strcasecmp($type, "g")) && !is_integer($status)){
8214            return false;
8215        }
8216        if (!strcasecmp($type, "i")) {
8217            if (strcasecmp(get_class($reviewer), $dms->getClassname("user"))) {
8218                return false;
8219            }
8220            if ($this->_indReviewers == null) {
8221                $this->_indReviewers = array();
8222            }
8223            $this->_indReviewers[$status][] = $reviewer;
8224        }
8225        if (!strcasecmp($type, "g")) {
8226            if (strcasecmp(get_class($reviewer), $dms->getClassname("group"))) {
8227                return false;
8228            }
8229            if ($this->_grpReviewers == null) {
8230                $this->_grpReviewers = array();
8231            }
8232            $this->_grpReviewers[$status][] = $reviewer;
8233        }
8234        return true;
8235    } /* }}} */
8236
8237    /**
8238     * @param $approver
8239     * @param $type
8240     * @param $status
8241     * @return bool
8242     */
8243    public function addApprover($approver, $type, $status) { /* {{{ */
8244        $dms = $this->_dms;
8245
8246        if (!is_object($approver) || (strcasecmp($type, "i") && strcasecmp($type, "g")) && !is_integer($status)){
8247            return false;
8248        }
8249        if (!strcasecmp($type, "i")) {
8250            if (strcasecmp(get_class($approver), $dms->getClassname("user"))) {
8251                return false;
8252            }
8253            if ($this->_indApprovers == null) {
8254                $this->_indApprovers = array();
8255            }
8256            $this->_indApprovers[$status][] = $approver;
8257        }
8258        if (!strcasecmp($type, "g")) {
8259            if (strcasecmp(get_class($approver), $dms->getClassname("group"))) {
8260                return false;
8261            }
8262            if ($this->_grpApprovers == null) {
8263                $this->_grpApprovers = array();
8264            }
8265            $this->_grpApprovers[$status][] = $approver;
8266        }
8267        return true;
8268    } /* }}} */
8269
8270    /**
8271     * @param $status
8272     * @return bool
8273     */
8274    public function setStatus($status) { /* {{{ */
8275        if (!is_integer($status)) {
8276            return false;
8277        }
8278        if ($status<-3 || $status>3) {
8279            return false;
8280        }
8281        $this->_status = $status;
8282        return true;
8283    } /* }}} */
8284
8285    /**
8286     * @return null
8287     */
8288    public function getStatus() { /* {{{ */
8289        return $this->_status;
8290    } /* }}} */
8291
8292    /**
8293     * @return mixed
8294     */
8295    public function getContent() { /* {{{ */
8296        return $this->_content;
8297    } /* }}} */
8298
8299    /**
8300     * @param $type
8301     * @return array|bool|null
8302     */
8303    public function getReviewers($type) { /* {{{ */
8304        if (strcasecmp($type, "i") && strcasecmp($type, "g")) {
8305            return false;
8306        }
8307        if (!strcasecmp($type, "i")) {
8308            return ($this->_indReviewers == null ? array() : $this->_indReviewers);
8309        }
8310        else {
8311            return ($this->_grpReviewers == null ? array() : $this->_grpReviewers);
8312        }
8313    } /* }}} */
8314
8315    /**
8316     * @param $type
8317     * @return array|bool|null
8318     */
8319    public function getApprovers($type) { /* {{{ */
8320        if (strcasecmp($type, "i") && strcasecmp($type, "g")) {
8321            return false;
8322        }
8323        if (!strcasecmp($type, "i")) {
8324            return ($this->_indApprovers == null ? array() : $this->_indApprovers);
8325        }
8326        else {
8327            return ($this->_grpApprovers == null ? array() : $this->_grpApprovers);
8328        }
8329    } /* }}} */
8330} /* }}} */